From 1f11388af7cffebdfe6cd7a800a15935be072139 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:12:08 -0400 Subject: [PATCH 01/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Brought over all of the remaining code and project changes. --- .../netcore/ref/Microsoft.Data.SqlClient.cs | 107 +-- .../ref/Microsoft.Data.SqlClient.csproj | 15 +- .../src/Microsoft.Data.SqlClient.csproj | 26 +- .../netfx/ref/Microsoft.Data.SqlClient.cs | 111 +-- .../netfx/ref/Microsoft.Data.SqlClient.csproj | 14 +- .../netfx/src/Microsoft.Data.SqlClient.csproj | 26 +- .../src/Microsoft.Data.SqlClient.csproj | 21 +- .../src/Microsoft/Data/Common/AdapterUtil.cs | 17 +- .../ActiveDirectoryAuthenticationProvider.cs | 743 ------------------ .../Connection/SqlConnectionInternal.cs | 250 ++---- .../SqlClient/SqlAuthenticationParameters.cs | 162 ---- .../SqlAuthenticationParametersBuilder.cs | 104 +++ .../SqlClient/SqlAuthenticationProvider.cs | 37 - .../SqlAuthenticationProviderManager.cs | 232 ++++-- .../Data/SqlClient/SqlAuthenticationToken.cs | 31 - .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 44 -- .../FunctionalTests/AADAuthenticationTests.cs | 45 +- .../SqlAuthenticationProviderManagerTests.cs | 35 + .../SqlAuthenticationProviderTest.cs | 41 - .../ManualTests/DataCommon/DataTestUtility.cs | 9 + .../DataCommon/ManagedIdentityProvider.cs | 97 +++ .../DataCommon/UsernamePasswordProvider.cs | 72 ++ ....Data.SqlClient.ManualTesting.Tests.csproj | 2 + .../AADFedAuthTokenRefreshTest.cs | 23 +- .../ConnectivityTests/AADConnectionTest.cs | 391 +++------ .../StressTests/Directory.Packages.props | 17 +- .../SqlClient.Stress.Framework.csproj | 1 + .../SqlAuthenticationProviderManagerTests.cs | 76 ++ tools/specs/Microsoft.Data.SqlClient.nuspec | 11 + 29 files changed, 839 insertions(+), 1921 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs delete mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs delete mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 3cad874d58..58da74a33d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -43,9 +43,9 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } /// public sealed class SqlDataSourceEnumerator : System.Data.Common.DbDataSourceEnumerator { - /// + /// public static SqlDataSourceEnumerator Instance { get; } - /// + /// public override System.Data.DataTable GetDataSources() { throw null; } } } @@ -141,30 +141,6 @@ public SqlVector(System.ReadOnlyMemory memory) { } } namespace Microsoft.Data.SqlClient { - /// - public sealed partial class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - public ActiveDirectoryAuthenticationProvider() { } - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) { } - /// - public static void ClearUserTokenCache() { } - /// - public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod, string applicationClientId = null) { } - /// - public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } - /// - public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) { } - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) { } - } /// public enum ApplicationIntent { @@ -203,85 +179,6 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod - { - /// - NotSpecified = 0, - /// - SqlPassword = 1, - /// - [System.Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword = 2, - /// - ActiveDirectoryIntegrated = 3, - /// - ActiveDirectoryInteractive = 4, - /// - ActiveDirectoryServicePrincipal = 5, - /// - ActiveDirectoryDeviceCodeFlow = 6, - /// - ActiveDirectoryManagedIdentity = 7, - /// - ActiveDirectoryMSI = 8, - /// - ActiveDirectoryDefault = 9, - /// - ActiveDirectoryWorkloadIdentity = 10 - } - /// - public class SqlAuthenticationParameters - { - /// - protected SqlAuthenticationParameters(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, string serverName, string databaseName, string resource, string authority, string userId, string password, System.Guid connectionId, int connectionTimeout) { } - /// - public Microsoft.Data.SqlClient.SqlAuthenticationMethod AuthenticationMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Authority { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public System.Guid ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string DatabaseName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string ServerName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public string UserId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public int ConnectionTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - } - /// - public abstract partial class SqlAuthenticationProvider - { - /// - protected SqlAuthenticationProvider() { } - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public virtual void BeforeUnload(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public static Microsoft.Data.SqlClient.SqlAuthenticationProvider GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { throw null; } - /// - public abstract bool IsSupported(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod); - /// - public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; } - } - /// - public partial class SqlAuthenticationToken - { - /// - public SqlAuthenticationToken(string accessToken, System.DateTimeOffset expiresOn) { } - /// - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - /// - public System.DateTimeOffset ExpiresOn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } - } /// public sealed partial class SqlBulkCopy : System.IDisposable { diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 871db24225..d23cdbc728 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -6,7 +6,7 @@ $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ netcoreapp $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ - $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netstandard\ $(OutputPath)\$(TargetFramework)\$(AssemblyName).xml @@ -39,8 +39,6 @@ - - @@ -57,6 +55,17 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index c5cc017919..b09e9a66fb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -249,9 +249,6 @@ Microsoft\Data\SqlClient\AAsyncCallContext.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs @@ -654,18 +651,12 @@ Microsoft\Data\SqlClient\SqlAppContextSwitchManager.netcore.cs - - Microsoft\Data\SqlClient\SqlAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs - - Microsoft\Data\SqlClient\SqlAuthenticationToken.cs - Microsoft\Data\SqlClient\SqlBatch.cs @@ -1080,8 +1071,6 @@ - - @@ -1092,6 +1081,17 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 1647dfe94c..049d91fe86 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -47,41 +47,15 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } /// public sealed class SqlDataSourceEnumerator : System.Data.Common.DbDataSourceEnumerator { - /// + /// public static SqlDataSourceEnumerator Instance {get;} - /// + /// public override System.Data.DataTable GetDataSources(){ throw null; } } } namespace Microsoft.Data.SqlClient { - /// - public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - public ActiveDirectoryAuthenticationProvider() { } - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) { } - /// - public static void ClearUserTokenCache() { } - /// - public ActiveDirectoryAuthenticationProvider(System.Func deviceCodeFlowCallbackMethod, string applicationClientId = null) { } - /// - public override System.Threading.Tasks.Task AcquireTokenAsync(SqlAuthenticationParameters parameters) { throw null; } - /// - public void SetDeviceCodeFlowCallback(System.Func deviceCodeFlowCallbackMethod) { } - /// - public void SetAcquireAuthorizationCodeAsyncCallback(System.Func> acquireAuthorizationCodeAsyncCallback) { } - /// - public void SetIWin32WindowFunc(System.Func iWin32WindowFunc) { } - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) { throw null; } - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) { } - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) { } - } /// public enum ApplicationIntent { @@ -121,85 +95,6 @@ protected SqlAuthenticationInitializer() { } /// public abstract void Initialize(); } - /// - public enum SqlAuthenticationMethod - { - /// - NotSpecified = 0, - /// - SqlPassword = 1, - /// - [System.ObsoleteAttribute("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword = 2, - /// - ActiveDirectoryIntegrated = 3, - /// - ActiveDirectoryInteractive = 4, - /// - ActiveDirectoryServicePrincipal = 5, - /// - ActiveDirectoryDeviceCodeFlow = 6, - /// - ActiveDirectoryManagedIdentity = 7, - /// - ActiveDirectoryMSI = 8, - /// - ActiveDirectoryDefault = 9, - /// - ActiveDirectoryWorkloadIdentity = 10 - } - /// - public class SqlAuthenticationParameters - { - /// - protected SqlAuthenticationParameters(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, string serverName, string databaseName, string resource, string authority, string userId, string password, System.Guid connectionId, int connectionTimeout) { } - /// - public Microsoft.Data.SqlClient.SqlAuthenticationMethod AuthenticationMethod { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Authority { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public System.Guid ConnectionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string DatabaseName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Password { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string Resource { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string ServerName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public string UserId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public int ConnectionTimeout { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - /// - public abstract partial class SqlAuthenticationProvider - { - /// - protected SqlAuthenticationProvider() { } - /// - public abstract System.Threading.Tasks.Task AcquireTokenAsync(Microsoft.Data.SqlClient.SqlAuthenticationParameters parameters); - /// - public virtual void BeforeLoad(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public virtual void BeforeUnload(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { } - /// - public static Microsoft.Data.SqlClient.SqlAuthenticationProvider GetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod) { throw null; } - /// - public abstract bool IsSupported(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod); - /// - public static bool SetProvider(Microsoft.Data.SqlClient.SqlAuthenticationMethod authenticationMethod, Microsoft.Data.SqlClient.SqlAuthenticationProvider provider) { throw null; } - } - /// - public partial class SqlAuthenticationToken - { - /// - public SqlAuthenticationToken(string accessToken, System.DateTimeOffset expiresOn) { } - /// - public string AccessToken { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public System.DateTimeOffset ExpiresOn { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } /// public sealed partial class SqlBulkCopy : System.IDisposable { @@ -2821,7 +2716,7 @@ public SqlJson(string jsonString) { } /// public SqlJson(System.Text.Json.JsonDocument jsonDoc) { } /// - public bool IsNull => throw null; + public bool IsNull => throw null; /// public static SqlJson Null => throw null; /// diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index 557873ae77..665e47dd76 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -40,8 +40,6 @@ - - All @@ -57,5 +55,17 @@ + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index b8276bcb29..6a00753f81 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -313,9 +313,6 @@ Microsoft\Data\SqlClient\AAsyncCallContext.cs - - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationProvider.cs - Microsoft\Data\SqlClient\ActiveDirectoryAuthenticationTimeoutRetryHelper.cs @@ -652,18 +649,12 @@ Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Factory.cs - - Microsoft\Data\SqlClient\SqlAuthenticationParameters.cs - - - Microsoft\Data\SqlClient\SqlAuthenticationProvider.cs + + Microsoft\Data\SqlClient\SqlAuthenticationParametersBuilder.cs Microsoft\Data\SqlClient\SqlAuthenticationProviderManager.cs - - Microsoft\Data\SqlClient\SqlAuthenticationToken.cs - Microsoft\Data\SqlClient\SqlBatchCommand.cs @@ -1063,8 +1054,6 @@ - - All @@ -1083,6 +1072,17 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 8505df0724..df6fc79b5d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -27,7 +27,7 @@ net462;net8.0;net9.0 - + @@ -57,7 +57,7 @@ $(TargetOs.ToLower()) - + $(DefineConstants);_UNIX @@ -111,9 +111,7 @@ - - - + All @@ -134,8 +132,6 @@ - - @@ -146,6 +142,17 @@ + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index abbeeda0eb..16a482d4de 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -21,7 +21,6 @@ using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.Connection; -using Microsoft.Identity.Client; using Microsoft.SqlServer.Server; using IsolationLevel = System.Data.IsolationLevel; @@ -502,7 +501,7 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName)); internal static Exception CreateSqlException( - MsalException msalException, + SqlAuthenticationProviderException authException, SqlConnectionString connectionOptions, SqlConnectionInternal sender, string username) @@ -513,20 +512,20 @@ internal static Exception CreateSqlException( sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, connectionOptions.DataSource, StringsHelper.GetString(Strings.SQL_MSALFailure, username, connectionOptions.Authentication.ToString("G")), - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + authException.Method.ToString(), 0)); // Error[1] - string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode); + string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, authException.FailureCode); sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, - connectionOptions.DataSource, errorMessage1, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + connectionOptions.DataSource, errorMessage1, + authException.Method.ToString(), 0)); // Error[2] - if (!string.IsNullOrEmpty(msalException.Message)) + if (!string.IsNullOrEmpty(authException.Message)) { sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, - connectionOptions.DataSource, msalException.Message, - ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0)); + connectionOptions.DataSource, authException.Message, + authException.Method.ToString(), 0)); } return SqlException.CreateException(sqlErs, "", sender, innerException: null, batchCommand: null); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs deleted file mode 100644 index 0393de9beb..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ /dev/null @@ -1,743 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Concurrent; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Azure.Core; -using Azure.Identity; -using Microsoft.Data.Common.ConnectionString; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Identity.Client; -using Microsoft.Identity.Client.Extensibility; - -namespace Microsoft.Data.SqlClient -{ - /// - public sealed class ActiveDirectoryAuthenticationProvider : SqlAuthenticationProvider - { - /// - /// This is a static cache instance meant to hold instances of "PublicClientApplication" mapping to information available in PublicClientAppKey. - /// The purpose of this cache is to allow re-use of Access Tokens fetched for a user interactively or with any other mode - /// to avoid interactive authentication request every-time, within application scope making use of MSAL's userTokenCache. - /// - private static readonly ConcurrentDictionary s_pcaMap = new(); - private static readonly ConcurrentDictionary s_tokenCredentialMap = new(); - private static SemaphoreSlim s_pcaMapModifierSemaphore = new(1, 1); - private static SemaphoreSlim s_tokenCredentialMapModifierSemaphore = new(1, 1); - private static readonly MemoryCache s_accountPwCache = new MemoryCache(new MemoryCacheOptions()); - private static readonly int s_accountPwCacheTtlInHours = 2; - private static readonly string s_nativeClientRedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"; - private static readonly string s_defaultScopeSuffix = "/.default"; - private readonly string _type = typeof(ActiveDirectoryAuthenticationProvider).Name; - private readonly SqlClientLogger _logger = new(); - private Func _deviceCodeFlowCallback; - private ICustomWebUi _customWebUI = null; - private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; - - /// - public ActiveDirectoryAuthenticationProvider() - : this(DefaultDeviceFlowCallback) - { - } - - /// - public ActiveDirectoryAuthenticationProvider(string applicationClientId) - : this(DefaultDeviceFlowCallback, applicationClientId) - { - } - - /// - public ActiveDirectoryAuthenticationProvider(Func deviceCodeFlowCallbackMethod, string applicationClientId = null) - { - if (applicationClientId != null) - { - _applicationClientId = applicationClientId; - } - SetDeviceCodeFlowCallback(deviceCodeFlowCallbackMethod); - } - - /// - public static void ClearUserTokenCache() - { - if (!s_pcaMap.IsEmpty) - { - s_pcaMap.Clear(); - } - - if (!s_tokenCredentialMap.IsEmpty) - { - s_tokenCredentialMap.Clear(); - } - } - - /// - public void SetDeviceCodeFlowCallback(Func deviceCodeFlowCallbackMethod) => _deviceCodeFlowCallback = deviceCodeFlowCallbackMethod; - - /// - public void SetAcquireAuthorizationCodeAsyncCallback(Func> acquireAuthorizationCodeAsyncCallback) => _customWebUI = new CustomWebUi(acquireAuthorizationCodeAsyncCallback); - - /// - public override bool IsSupported(SqlAuthenticationMethod authentication) - { - return authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated - #pragma warning disable 0618 // Type or member is obsolete - || authentication == SqlAuthenticationMethod.ActiveDirectoryPassword - #pragma warning restore 0618 // Type or member is obsolete - || authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal - || authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow - || authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity - || authentication == SqlAuthenticationMethod.ActiveDirectoryMSI - || authentication == SqlAuthenticationMethod.ActiveDirectoryDefault - || authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; - } - - /// - public override void BeforeLoad(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeLoad", $"being loaded into SqlAuthProviders for {authentication}."); - } - - /// - public override void BeforeUnload(SqlAuthenticationMethod authentication) - { - _logger.LogInfo(_type, "BeforeUnload", $"being unloaded from SqlAuthProviders for {authentication}."); - } - -#if NETFRAMEWORK - private Func _iWin32WindowFunc = null; - - /// - public void SetIWin32WindowFunc(Func iWin32WindowFunc) => this._iWin32WindowFunc = iWin32WindowFunc; -#endif - - /// - - public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) - { - using CancellationTokenSource cts = new(); - - // Use Connection timeout value to cancel token acquire request after certain period of time. - int timeout = parameters.ConnectionTimeout * 1000; // Convert to milliseconds - if (timeout > 0) // if ConnectionTimeout is 0 or the millis overflows an int, no need to set CancelAfter - { - cts.CancelAfter(timeout); - } - - string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; - string[] scopes = new string[] { scope }; - TokenRequestContext tokenRequestContext = new(scopes); - - /* We split audience from Authority URL here. Audience can be one of the following: - * The Azure AD authority audience enumeration - * The tenant ID, which can be: - * - A GUID (the ID of your Azure AD instance), for single-tenant applications - * - A domain name associated with your Azure AD instance (also for single-tenant applications) - * One of these placeholders as a tenant ID in place of the Azure AD authority audience enumeration: - * - `organizations` for a multitenant application - * - `consumers` to sign in users only with their personal accounts - * - `common` to sign in users with their work and school accounts or their personal Microsoft accounts - * - * MSAL will throw a meaningful exception if you specify both the Azure AD authority audience and the tenant ID. - * If you don't specify an audience, your app will target Azure AD and personal Microsoft accounts as an audience. (That is, it will behave as though `common` were specified.) - * More information: https://docs.microsoft.com/azure/active-directory/develop/msal-client-application-configuration - **/ - - int separatorIndex = parameters.Authority.LastIndexOf('/'); - string authority = parameters.Authority.Remove(separatorIndex + 1); - string audience = parameters.Authority.Substring(separatorIndex + 1); - string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId; - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault) - { - // Cache DefaultAzureCredenial based on scope, authority, audience, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(DefaultAzureCredential), authority, scope, audience, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(authority) }; - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryMSI) - { - // Cache ManagedIdentityCredential based on scope, authority, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(ManagedIdentityCredential), authority, scope, string.Empty, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Managed Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal) - { - // Cache ClientSecretCredential based on scope, authority, audience, and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(ClientSecretCredential), authority, scope, audience, clientId); - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, parameters.Password, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity) - { - // Cache WorkloadIdentityCredential based on authority and clientId - TokenCredentialKey tokenCredentialKey = new(typeof(WorkloadIdentityCredential), authority, string.Empty, string.Empty, clientId); - // If either tenant id, client id, or the token file path are not specified when fetching the token, - // a CredentialUnavailableException will be thrown instead - AccessToken accessToken = await GetTokenAsync(tokenCredentialKey, string.Empty, tokenRequestContext, cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Workload Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn); - return new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn); - } - - /* - * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows - * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend - * that you use https://login.microsoftonline.com/common/oauth2/nativeclient. - * - * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris - */ - string redirectUri = s_nativeClientRedirectUri; - -#if NET - if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) - { - redirectUri = "http://localhost"; - } -#endif - PublicClientAppKey pcaKey = new(parameters.Authority, redirectUri, _applicationClientId -#if NETFRAMEWORK - , _iWin32WindowFunc -#endif - ); - - AuthenticationResult result = null; - IPublicClientApplication app = await GetPublicClientAppInstanceAsync(pcaKey, cts.Token).ConfigureAwait(false); - - if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - - if (result == null) - { - if (!string.IsNullOrEmpty(parameters.UserId)) - { - // The AcquireTokenByIntegratedWindowsAuth method is marked as obsolete in MSAL.NET - // but it is still a supported way to acquire tokens for Active Directory Integrated authentication. -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .WithUsername(parameters.UserId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - else - { -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - } - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - } - #pragma warning disable 0618 // Type or member is obsolete - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword) - #pragma warning restore 0618 // Type or member is obsolete - { - string pwCacheKey = GetAccountPwCacheKey(parameters); - object previousPw = s_accountPwCache.Get(pwCacheKey); - byte[] currPwHash = GetHash(parameters.Password); - - if (previousPw != null && - previousPw is byte[] previousPwBytes && - // Only get the cached token if the current password hash matches the previously used password hash - AreEqual(currPwHash, previousPwBytes)) - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - } - - if (result == null) - { -#pragma warning disable CS0618 // Type or member is obsolete - result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) -#pragma warning restore CS0618 // Type or member is obsolete - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - - // We cache the password hash to ensure future connection requests include a validated password - // when we check for a cached MSAL account. Otherwise, a connection request with the same username - // against the same tenant could succeed with an invalid password when we re-use the cached token. - using (ICacheEntry entry = s_accountPwCache.CreateEntry(pwCacheKey)) - { - entry.Value = GetHash(parameters.Password); - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(s_accountPwCacheTtlInHours); - } - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn); - } - } - else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive || - parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) - { - try - { - result = await TryAcquireTokenSilent(app, parameters, scopes, cts).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - catch (MsalUiRequiredException) - { - // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application, - // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired), - // or the user needs to perform two factor authentication. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - - if (result == null) - { - // If no existing 'account' is found, we request user to sign in interactively. - result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts, _customWebUI, _deviceCodeFlowCallback).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod); - throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod); - } - - return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); - } - - private static async Task TryAcquireTokenSilent(IPublicClientApplication app, SqlAuthenticationParameters parameters, - string[] scopes, CancellationTokenSource cts) - { - AuthenticationResult result = null; - - // Fetch available accounts from 'app' instance - System.Collections.Generic.IEnumerator accounts = (await app.GetAccountsAsync().ConfigureAwait(false)).GetEnumerator(); - - IAccount account = default; - if (accounts.MoveNext()) - { - if (!string.IsNullOrEmpty(parameters.UserId)) - { - do - { - IAccount currentVal = accounts.Current; - if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0) - { - account = currentVal; - break; - } - } - while (accounts.MoveNext()); - } - else - { - account = accounts.Current; - } - } - - if (account != null) - { - // If 'account' is available in 'app', we use the same to acquire token silently. - // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent - result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token).ConfigureAwait(false); - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn); - } - - return result; - } - - private static async Task AcquireTokenInteractiveDeviceFlowAsync(IPublicClientApplication app, string[] scopes, Guid connectionId, string userId, - SqlAuthenticationMethod authenticationMethod, CancellationTokenSource cts, ICustomWebUi customWebUI, Func deviceCodeFlowCallback) - { - try - { - if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) - { - CancellationTokenSource ctsInteractive = new(); -#if NET - /* - * On .NET Core, MSAL will start the system browser as a separate process. MSAL does not have control over this browser, - * but once the user finishes authentication, the web page is redirected in such a way that MSAL can intercept the Uri. - * MSAL cannot detect if the user navigates away or simply closes the browser. Apps using this technique are encouraged - * to define a timeout (via CancellationToken). We recommend a timeout of at least a few minutes, to take into account - * cases where the user is prompted to change password or perform 2FA. - * - * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core#system-browser-experience - */ - ctsInteractive.CancelAfter(180000); -#endif - if (customWebUI != null) - { - return await app.AcquireTokenInteractive(scopes) - .WithCorrelationId(connectionId) - .WithCustomWebUi(customWebUI) - .WithLoginHint(userId) - .ExecuteAsync(ctsInteractive.Token) - .ConfigureAwait(false); - } - else - { - /* - * We will use the MSAL Embedded or System web browser which changes by Default in MSAL according to this table: - * - * Framework Embedded System Default - * ------------------------------------------- - * .NET Classic Yes Yes^ Embedded - * .NET Core No Yes^ System - * .NET Standard No No NONE - * UWP Yes No Embedded - * Xamarin.Android Yes Yes System - * Xamarin.iOS Yes Yes System - * Xamarin.Mac Yes No Embedded - * - * ^ Requires "http://localhost" redirect URI - * - * https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/MSAL.NET-uses-web-browser#at-a-glance - */ - return await app.AcquireTokenInteractive(scopes) - .WithCorrelationId(connectionId) - .WithLoginHint(userId) - .ExecuteAsync(ctsInteractive.Token) - .ConfigureAwait(false); - } - } - else - { - AuthenticationResult result = await app.AcquireTokenWithDeviceCode(scopes, - deviceCodeResult => deviceCodeFlowCallback(deviceCodeResult)) - .WithCorrelationId(connectionId) - .ExecuteAsync(cancellationToken: cts.Token) - .ConfigureAwait(false); - return result; - } - } - catch (OperationCanceledException) - { - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenInteractiveDeviceFlowAsync | Operation timed out while acquiring access token."); - throw (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive) ? - SQL.ActiveDirectoryInteractiveTimeout() : - SQL.ActiveDirectoryDeviceFlowTimeout(); - } - } - - private static Task DefaultDeviceFlowCallback(DeviceCodeResult result) - { - // This will print the message on the console which tells the user where to go sign-in using - // a separate browser and the code to enter once they sign in. - // The AcquireTokenWithDeviceCode() method will poll the server after firing this - // device code callback to look for the successful login of the user via that browser. - // This background polling (whose interval and timeout data is also provided as fields in the - // deviceCodeCallback class) will occur until: - // * The user has successfully logged in via browser and entered the proper code - // * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached - // * The developing application calls the Cancel() method on a CancellationToken sent into the method. - // If this occurs, an OperationCanceledException will be thrown (see catch below for more details). - SqlClientEventSource.Log.TryTraceEvent("AcquireTokenInteractiveDeviceFlowAsync | Callback triggered with Device Code Result: {0}", result.Message); - Console.WriteLine(result.Message); - return Task.FromResult(0); - } - - private class CustomWebUi : ICustomWebUi - { - private readonly Func> _acquireAuthorizationCodeAsyncCallback; - - internal CustomWebUi(Func> acquireAuthorizationCodeAsyncCallback) => _acquireAuthorizationCodeAsyncCallback = acquireAuthorizationCodeAsyncCallback; - - public Task AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUri, CancellationToken cancellationToken) - => _acquireAuthorizationCodeAsyncCallback.Invoke(authorizationUri, redirectUri, cancellationToken); - } - - private async Task GetPublicClientAppInstanceAsync(PublicClientAppKey publicClientAppKey, CancellationToken cancellationToken) - { - if (!s_pcaMap.TryGetValue(publicClientAppKey, out IPublicClientApplication clientApplicationInstance)) - { - await s_pcaMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - // Double-check in case another thread added it while we waited for the semaphore - if (!s_pcaMap.TryGetValue(publicClientAppKey, out clientApplicationInstance)) - { - clientApplicationInstance = CreateClientAppInstance(publicClientAppKey); - s_pcaMap.TryAdd(publicClientAppKey, clientApplicationInstance); - } - } - finally - { - s_pcaMapModifierSemaphore.Release(); - } - } - - return clientApplicationInstance; - } - - private static async Task GetTokenAsync(TokenCredentialKey tokenCredentialKey, string secret, - TokenRequestContext tokenRequestContext, CancellationToken cancellationToken) - { - if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out TokenCredentialData tokenCredentialInstance)) - { - await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - // Double-check in case another thread added it while we waited for the semaphore - if (!s_tokenCredentialMap.TryGetValue(tokenCredentialKey, out tokenCredentialInstance)) - { - tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); - s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); - } - } - finally - { - s_tokenCredentialMapModifierSemaphore.Release(); - } - } - - if (!AreEqual(tokenCredentialInstance._secretHash, GetHash(secret))) - { - // If the secret hash has changed, we need to remove the old token credential instance and create a new one. - await s_tokenCredentialMapModifierSemaphore.WaitAsync(cancellationToken); - try - { - s_tokenCredentialMap.TryRemove(tokenCredentialKey, out _); - tokenCredentialInstance = CreateTokenCredentialInstance(tokenCredentialKey, secret); - s_tokenCredentialMap.TryAdd(tokenCredentialKey, tokenCredentialInstance); - } - finally - { - s_tokenCredentialMapModifierSemaphore.Release(); - } - } - - return await tokenCredentialInstance._tokenCredential.GetTokenAsync(tokenRequestContext, cancellationToken); - } - - private static string GetAccountPwCacheKey(SqlAuthenticationParameters parameters) - { - return parameters.Authority + "+" + parameters.UserId; - } - - private static byte[] GetHash(string input) - { - byte[] unhashedBytes = Encoding.Unicode.GetBytes(input); - SHA256 sha256 = SHA256.Create(); - byte[] hashedBytes = sha256.ComputeHash(unhashedBytes); - return hashedBytes; - } - - private static bool AreEqual(byte[] a1, byte[] a2) - { - if (ReferenceEquals(a1, a2)) - { - return true; - } - else if (a1 is null || a2 is null) - { - return false; - } - else if (a1.Length != a2.Length) - { - return false; - } - - return a1.AsSpan().SequenceEqual(a2.AsSpan()); - } - - private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) - { - PublicClientApplicationBuilder builder = PublicClientApplicationBuilder - .CreateWithApplicationOptions(new PublicClientApplicationOptions - { - ClientId = publicClientAppKey._applicationClientId, - ClientName = DbConnectionStringDefaults.ApplicationName, - ClientVersion = Common.ADP.GetAssemblyVersion().ToString(), - RedirectUri = publicClientAppKey._redirectUri, - }) - .WithAuthority(publicClientAppKey._authority); - - #if NETFRAMEWORK - if (_iWin32WindowFunc is not null) - { - builder.WithParentActivityOrWindow(_iWin32WindowFunc); - } - #endif - - return builder.Build(); - } - - private static TokenCredentialData CreateTokenCredentialInstance(TokenCredentialKey tokenCredentialKey, string secret) - { - if (tokenCredentialKey._tokenCredentialType == typeof(DefaultAzureCredential)) - { - DefaultAzureCredentialOptions defaultAzureCredentialOptions = new() - { - AuthorityHost = new Uri(tokenCredentialKey._authority), - TenantId = tokenCredentialKey._audience, - ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications. - }; - - // Optionally set clientId when available - if (tokenCredentialKey._clientId is not null) - { - defaultAzureCredentialOptions.ManagedIdentityClientId = tokenCredentialKey._clientId; -#pragma warning disable CS0618 // Type or member is obsolete - defaultAzureCredentialOptions.SharedTokenCacheUsername = tokenCredentialKey._clientId; -#pragma warning restore CS0618 // Type or member is obsolete - defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; - } - - // SqlClient is a library and provides support to acquire access - // token using 'DefaultAzureCredential' on user demand when they - // specify 'Authentication = Active Directory Default' in - // connection string. - // - // Default Azure Credential is instantiated by the calling - // application when using "Active Directory Default" - // authentication code to connect to Azure SQL instance. - // SqlClient is a library, doesn't instantiate the credential - // without running application instructions. - // - // Note that CodeQL suppression support can only detect - // suppression comments that appear immediately above the - // flagged statement, or appended to the end of the statement. - // Multi-line justifications are not supported. - // - // https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/codeql/codeql-semmle#guidance-on-suppressions - // - // CodeQL [SM05137] See above for justification. - DefaultAzureCredential cred = new(defaultAzureCredentialOptions); - - return new TokenCredentialData(cred, GetHash(secret)); - } - - TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; - - if (tokenCredentialKey._tokenCredentialType == typeof(ManagedIdentityCredential)) - { - return new TokenCredentialData(new ManagedIdentityCredential(tokenCredentialKey._clientId, tokenCredentialOptions), GetHash(secret)); - } - else if (tokenCredentialKey._tokenCredentialType == typeof(ClientSecretCredential)) - { - return new TokenCredentialData(new ClientSecretCredential(tokenCredentialKey._audience, tokenCredentialKey._clientId, secret, tokenCredentialOptions), GetHash(secret)); - } - else if (tokenCredentialKey._tokenCredentialType == typeof(WorkloadIdentityCredential)) - { - // The WorkloadIdentityCredentialOptions object initialization populates its instance members - // from the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_FEDERATED_TOKEN_FILE, - // and AZURE_ADDITIONALLY_ALLOWED_TENANTS. AZURE_CLIENT_ID may be overridden by the User Id. - WorkloadIdentityCredentialOptions options = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; - - if (tokenCredentialKey._clientId is not null) - { - options.ClientId = tokenCredentialKey._clientId; - } - - return new TokenCredentialData(new WorkloadIdentityCredential(options), GetHash(secret)); - } - - // This should never be reached, but if it is, throw an exception that will be noticed during development - throw new ArgumentException(nameof(ActiveDirectoryAuthenticationProvider)); - } - - internal class PublicClientAppKey - { - public readonly string _authority; - public readonly string _redirectUri; - public readonly string _applicationClientId; -#if NETFRAMEWORK - public readonly Func _iWin32WindowFunc; -#endif - - public PublicClientAppKey(string authority, string redirectUri, string applicationClientId -#if NETFRAMEWORK - , Func iWin32WindowFunc -#endif - ) - { - _authority = authority; - _redirectUri = redirectUri; - _applicationClientId = applicationClientId; -#if NETFRAMEWORK - _iWin32WindowFunc = iWin32WindowFunc; -#endif - } - - public override bool Equals(object obj) - { - if (obj != null && obj is PublicClientAppKey pcaKey) - { - return (string.CompareOrdinal(_authority, pcaKey._authority) == 0 - && string.CompareOrdinal(_redirectUri, pcaKey._redirectUri) == 0 - && string.CompareOrdinal(_applicationClientId, pcaKey._applicationClientId) == 0 -#if NETFRAMEWORK - && pcaKey._iWin32WindowFunc == _iWin32WindowFunc -#endif - ); - } - return false; - } - - public override int GetHashCode() => Tuple.Create(_authority, _redirectUri, _applicationClientId -#if NETFRAMEWORK - , _iWin32WindowFunc -#endif - ).GetHashCode(); - } - - internal class TokenCredentialData - { - public TokenCredential _tokenCredential; - public byte[] _secretHash; - - public TokenCredentialData(TokenCredential tokenCredential, byte[] secretHash) - { - _tokenCredential = tokenCredential; - _secretHash = secretHash; - } - } - - internal class TokenCredentialKey - { - public readonly Type _tokenCredentialType; - public readonly string _authority; - public readonly string _scope; - public readonly string _audience; - public readonly string _clientId; - - public TokenCredentialKey(Type tokenCredentialType, string authority, string scope, string audience, string clientId) - { - _tokenCredentialType = tokenCredentialType; - _authority = authority; - _scope = scope; - _audience = audience; - _clientId = clientId; - } - - public override bool Equals(object obj) - { - if (obj != null && obj is TokenCredentialKey tcKey) - { - return string.CompareOrdinal(nameof(_tokenCredentialType), nameof(tcKey._tokenCredentialType)) == 0 - && string.CompareOrdinal(_authority, tcKey._authority) == 0 - && string.CompareOrdinal(_scope, tcKey._scope) == 0 - && string.CompareOrdinal(_audience, tcKey._audience) == 0 - && string.CompareOrdinal(_clientId, tcKey._clientId) == 0 - ; - } - return false; - } - - public override int GetHashCode() => Tuple.Create(_tokenCredentialType, _authority, _scope, _audience, _clientId).GetHashCode(); - } - - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 1922ae052f..efd54ae383 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Net.Http.Headers; using System.Security; using System.Text; using System.Threading; @@ -19,7 +18,6 @@ using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.Connection; using Microsoft.Data.SqlClient.ConnectionPool; -using Microsoft.Identity.Client; using IsolationLevel = System.Data.IsolationLevel; namespace Microsoft.Data.SqlClient.Connection @@ -34,13 +32,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable // @TODO: Can be private? internal const int MaxNumberOfRedirectRoute = 10; - /// - /// Status code that indicates MSAL request should be retried. - /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 - /// - // @TODO: Can be private? - internal const int MsalHttpRetryStatusCode = 429; - /// /// The timespan defining the amount of time the authentication context needs to be valid /// for at-least, to re-use the cached context, without making an attempt to refresh it. IF @@ -132,20 +123,6 @@ internal class SqlConnectionInternal : DbConnectionInternal, IDisposable #region Debug/Test Behavior Overrides #if DEBUG - /// - /// This is a test hook to enable testing of the retry paths for MSAL get access token. - /// - /// - /// Type type = typeof(SqlConnection).Assembly.GetType("Microsoft.Data.SqlClient.SQLInternalConnectionTds"); - /// FieldInfo field = type.GetField("_forceMsalRetry", BindingFlags.NonPublic | BindingFlags.Static); - /// if (field != null) - /// { - /// field.SetValue(null, true); - /// } - /// - /// @TODO: For unit tests, it should not be necessary to do this via reflection. - internal static bool _forceMsalRetry = false; - /// /// This is a test hook to simulate a token expiring within the next 45 minutes. /// @@ -2701,58 +2678,47 @@ private void FailoverPermissionDemand() => PoolGroupProviderInfo?.FailoverPermissionDemand(); #endif + #nullable enable + /// /// Get the Federated Authentication Token. /// /// Information obtained from server as Federated Authentication Info. private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); + // Number of milliseconds to sleep for the initial back off, if a + // retry period is not specified by the provider. + const int defaultRetryPeriod = 100; - // Number of milliseconds to sleep for the initial back off. - int sleepInterval = 100; - - // Number of attempts, for tracing purposes, if we underwent retries. - int numberOfAttempts = 0; + // Number of attempts we are willing to perform. + const int maxAttempts = 1; // Username to use in error messages. - string username = null; - - SqlAuthenticationProvider authProvider = - SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + string? username = null; + SqlAuthenticationProvider? authProvider = SqlAuthenticationProviderManager.GetProvider(ConnectionOptions.Authentication); if (authProvider == null && _accessTokenCallback == null) { throw SQL.CannotFindAuthProvider(ConnectionOptions.Authentication.ToString()); } - // Retry getting access token once if MsalException.error_code is unknown_error. - // extra logic to deal with HTTP 429 (Retry after). - // @TODO: Can we pick one or the other? - // @TODO: Wait ... are we counting up but only looping while the number of attempts is <=1 ? Huh? - // @TODO: Can we consider using a for loop here since there's a fixed number of times to loop - - #if NET - while (numberOfAttempts <= 1) - #else - while (numberOfAttempts <= 1 && sleepInterval <= _timeout.MillisecondsRemaining) - #endif + // We will perform retries if the provider indicates an error that + // is retryable. + for (int attempt = 0; attempt <= maxAttempts; ++attempt) { - numberOfAttempts++; try { - var authParamsBuilder = new SqlAuthenticationParameters.Builder( - authenticationMethod: ConnectionOptions.Authentication, - resource: fedAuthInfo.Spn, - authority: fedAuthInfo.StsUrl, - serverName: ConnectionOptions.DataSource, - databaseName: ConnectionOptions.InitialCatalog) + var authParamsBuilder = new SqlAuthenticationParametersBuilder( + authenticationMethod: ConnectionOptions.Authentication, + resource: fedAuthInfo.Spn, + authority: fedAuthInfo.StsUrl, + serverName: ConnectionOptions.DataSource, + databaseName: ConnectionOptions.InitialCatalog) .WithConnectionId(_clientConnectionId) - .WithConnectionTimeout(ConnectionOptions.ConnectTimeout); + .WithAuthenticationTimeout(ConnectionOptions.ConnectTimeout); switch (ConnectionOptions.Authentication) { case SqlAuthenticationMethod.ActiveDirectoryIntegrated: - #if NET // In some scenarios for .NET Core, MSAL cannot detect the current user and needs it passed in // for Integrated auth. Allow the user/application to pass it in to work around those scenarios. if (!string.IsNullOrEmpty(ConnectionOptions.UserID)) @@ -2764,9 +2730,6 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; } - #else - username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; - #endif if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { @@ -2774,21 +2737,13 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) } else { - // We use Task.Run here in all places to execute task synchronously - // in the same context. Fixes block-over-async deadlock possibilities - // https://github.com/dotnet/SqlClient/issues/1209 - // @TODO: Verify that the wrapping/unwrapping is necessary. - _fedAuthToken = new( - Task.Run(async () => - await authProvider.AcquireTokenAsync(authParamsBuilder)) - .GetAwaiter() - .GetResult()); + // We use Task.Run here in all places to execute task synchronously in the same context. + // Fixes block-over-async deadlock possibilities https://github.com/dotnet/SqlClient/issues/1209 + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } - break; - case SqlAuthenticationMethod.ActiveDirectoryInteractive: case SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow: case SqlAuthenticationMethod.ActiveDirectoryManagedIdentity: @@ -2802,20 +2757,13 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) else { authParamsBuilder.WithUserId(ConnectionOptions.UserID); - _fedAuthToken = new( - Task.Run(async () => - await authProvider.AcquireTokenAsync(authParamsBuilder)) - .GetAwaiter() - .GetResult()); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } - break; - #pragma warning disable 0618 // Type or member is obsolete case SqlAuthenticationMethod.ActiveDirectoryPassword: #pragma warning restore 0618 // Type or member is obsolete - case SqlAuthenticationMethod.ActiveDirectoryServicePrincipal: if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { @@ -2823,32 +2771,21 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) } else { - // @TODO: _fedAuthToken assignment is identical in both cases - move outside if (_credential != null) { username = _credential.UserId; authParamsBuilder.WithUserId(username).WithPassword(_credential.Password); - _fedAuthToken = new( - Task.Run(async () => - await authProvider.AcquireTokenAsync(authParamsBuilder)) - .GetAwaiter() - .GetResult()); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } else { username = ConnectionOptions.UserID; authParamsBuilder.WithUserId(username).WithPassword(ConnectionOptions.Password); - _fedAuthToken = new( - Task.Run(async () => - await authProvider.AcquireTokenAsync(authParamsBuilder)) - .GetAwaiter() - .GetResult()); + _fedAuthToken = new(Task.Run(async () => await authProvider!.AcquireTokenAsync(authParamsBuilder)).GetAwaiter().GetResult()); } _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } - break; - default: if (_accessTokenCallback == null) { @@ -2871,23 +2808,14 @@ await authProvider.AcquireTokenAsync(authParamsBuilder)) authParamsBuilder.WithUserId(ConnectionOptions.UserID); authParamsBuilder.WithPassword(ConnectionOptions.Password); } - SqlAuthenticationParameters parameters = authParamsBuilder; - using CancellationTokenSource cts = new(); - - // Use Connection timeout value to cancel token acquire request - // after certain period of time.(int) - if (_timeout.MillisecondsRemaining < int.MaxValue) + CancellationTokenSource cts = new(); + // Use Connection timeout value to cancel token acquire request after certain period of time.(int) + if (_timeout.MillisecondsRemaining < Int32.MaxValue) { cts.CancelAfter((int)_timeout.MillisecondsRemaining); } - - _fedAuthToken = new( - Task.Run(async () => - await _accessTokenCallback(parameters, cts.Token)) - .GetAwaiter() - .GetResult()); - + _fedAuthToken = new(Task.Run(async () => await _accessTokenCallback(parameters, cts.Token)).GetAwaiter().GetResult()); _activeDirectoryAuthTimeoutRetryHelper.CachedToken = _fedAuthToken; } break; @@ -2895,103 +2823,42 @@ await _accessTokenCallback(parameters, cts.Token)) Debug.Assert(_fedAuthToken.AccessToken != null, "AccessToken should not be null."); - #if DEBUG - if (_forceMsalRetry) - { - // 3399614468 is 0xCAA20004L just for testing. - throw new MsalServiceException(MsalError.UnknownError, "Force retry in GetFedAuthToken"); - } - #endif - // Break out of the retry loop in successful case. break; } - catch (MsalServiceException serviceException) + catch (SqlAuthenticationProviderException ex) { - // Deal with Msal service exceptions first, retry if 429 received. - if (serviceException.StatusCode == MsalHttpRetryStatusCode) + // Is the error fatal or retryable? + if (!ex.ShouldRetry) { - RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter; - if (retryAfter.Delta.HasValue) - { - sleepInterval = retryAfter.Delta.Value.Milliseconds; - } - else if (retryAfter.Date.HasValue) - { - sleepInterval = Convert.ToInt32(retryAfter.Date.Value.Offset.TotalMilliseconds); - } + // It's fatal, so translate into a SqlException. + throw ADP.CreateSqlException(ex, ConnectionOptions, this, username); + } - // if there's enough time to retry before timeout, then retry, otherwise - // break out the retry loop. - if (sleepInterval < _timeout.MillisecondsRemaining) - { - Thread.Sleep(sleepInterval); - } - else - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException | ERR | " + - $"Timeout: {serviceException.ErrorCode}"); + // We should retry. - throw SQL.ActiveDirectoryTokenRetrievingTimeout( - Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), - serviceException.ErrorCode, - serviceException); - } - } - else + // Could we retry if we wanted to? + if (_timeout.IsExpired || _timeout.MillisecondsRemaining <= 0) { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException | ERR | " + - $"{serviceException.ErrorCode}"); - - throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username); + // No, so we throw. + SqlClientEventSource.Log.TryTraceEvent(" Attempt: {0}, Timeout: {1}", attempt, ex.FailureCode); + throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } - } - catch (MsalException msalException) - { - // Deal with normal MsalExceptions. - if (MsalError.UnknownError != msalException.ErrorCode || - _timeout.IsExpired || - _timeout.MillisecondsRemaining <= sleepInterval) - { - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken.MSALException | ERR | " + - $"{msalException.ErrorCode}"); - throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); - } + // We use a doubling backoff if the provider didn't provide + // a retry period. + int retryPeriod = + ex.RetryPeriod > 0 + ? ex.RetryPeriod + : defaultRetryPeriod * (2 ^ attempt); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken | ADV | " + - $"Object ID: {ObjectID}, " + - $"sleeping {sleepInterval}ms"); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken | ADV | " + - $"Object ID: {ObjectID}, " + - $"remaining {_timeout.MillisecondsRemaining}ms"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, attempt, retryPeriod); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, attempt, _timeout.MillisecondsRemaining); - Thread.Sleep(sleepInterval); + // Sleep for the desired period. + Thread.Sleep(retryPeriod); - sleepInterval *= 2; - } - // All other exceptions from MSAL/Azure Identity APIs - catch (Exception e) - { - SqlError error = new( - infoNumber: 0, - errorState: 0x00, - errorClass: TdsEnums.FATAL_ERROR_CLASS, - server: ConnectionOptions.DataSource, - errorMessage: e.Message, - procedure: ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, - lineNumber: 0); - - throw SqlException.CreateException( - error, - serverVersion: string.Empty, - internalConnection: this, - innerException: e); + // Fall through to retry... } } @@ -3014,19 +2881,14 @@ await _accessTokenCallback(parameters, cts.Token)) if (_dbConnectionPool != null) { DateTime expirationTime = DateTime.FromFileTimeUtc(_fedAuthToken.ExpirationFileTime); - _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext( - _fedAuthToken.AccessToken, - expirationTime); + _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(_fedAuthToken.AccessToken, expirationTime); } - - SqlClientEventSource.Log.TryTraceEvent( - $"SqlInternalConnectionTds.GetFedAuthToken | " + - $"Object ID {ObjectID}, " + - $"Finished generating federated authentication token."); - + SqlClientEventSource.Log.TryTraceEvent(" {0}, Finished generating federated authentication token.", ObjectID); return _fedAuthToken; } + #nullable disable + private void Login( ServerInfo server, TimeoutTimer timeout, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs deleted file mode 100644 index 9c74b937b8..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParameters.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using System.Security; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - - /// - public class SqlAuthenticationParameters - { - /// - public SqlAuthenticationMethod AuthenticationMethod { get; } - - /// - public string Resource { get; } - - /// - public string Authority { get; } - - /// - public string UserId { get; } - - /// - public string Password { get; } - - /// - public Guid ConnectionId { get; } - - /// - public string ServerName { get; } - - /// - public string DatabaseName { get; } - - /// - public int ConnectionTimeout { get; } = ADP.DefaultConnectionTimeout; - - /// - protected SqlAuthenticationParameters( - SqlAuthenticationMethod authenticationMethod, - string serverName, - string databaseName, - string resource, - string authority, - string userId, - string password, - Guid connectionId, - int connectionTimeout) - { - AuthenticationMethod = authenticationMethod; - ServerName = serverName; - DatabaseName = databaseName; - Resource = resource; - Authority = authority; - UserId = userId; - Password = password; - ConnectionId = connectionId; - ConnectionTimeout = connectionTimeout; - } - - /// - /// AD authentication parameter builder. - /// - internal class Builder - { - private readonly SqlAuthenticationMethod _authenticationMethod; - private readonly string _serverName; - private readonly string _databaseName; - private readonly string _resource; - private readonly string _authority; - private string _userId; - private string _password; - private Guid _connectionId = Guid.NewGuid(); - private int _connectionTimeout = ADP.DefaultConnectionTimeout; - - /// - /// Implicitly converts to . - /// - public static implicit operator SqlAuthenticationParameters(Builder builder) - { - return new SqlAuthenticationParameters( - authenticationMethod: builder._authenticationMethod, - serverName: builder._serverName, - databaseName: builder._databaseName, - resource: builder._resource, - authority: builder._authority, - userId: builder._userId, - password: builder._password, - connectionId: builder._connectionId, - connectionTimeout: builder._connectionTimeout); - } - - /// - /// Set user id. - /// - public Builder WithUserId(string userId) - { - _userId = userId; - return this; - } - - /// - /// Set password. - /// - public Builder WithPassword(string password) - { - _password = password; - return this; - } - - /// - /// Set password. - /// - public Builder WithPassword(SecureString password) - { - IntPtr valuePtr = IntPtr.Zero; - try - { - valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); - _password = Marshal.PtrToStringUni(valuePtr); - } - finally - { - Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); - } - return this; - } - - /// - /// Set a specific connection id instead of using a random one. - /// - public Builder WithConnectionId(Guid connectionId) - { - _connectionId = connectionId; - return this; - } - - /// - /// Set connection timeout. - /// - public Builder WithConnectionTimeout(int timeout) - { - _connectionTimeout = timeout; - return this; - } - - internal Builder(SqlAuthenticationMethod authenticationMethod, string resource, string authority, string serverName, string databaseName) - { - _authenticationMethod = authenticationMethod; - _serverName = serverName; - _databaseName = databaseName; - _resource = resource; - _authority = authority; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs new file mode 100644 index 0000000000..a9863ee2ac --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationParametersBuilder.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Security; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal sealed class SqlAuthenticationParametersBuilder + { + private readonly SqlAuthenticationMethod _authenticationMethod; + private readonly string _serverName; + private readonly string _databaseName; + private readonly string _resource; + private readonly string _authority; + private string _userId; + private string _password; + private Guid _connectionId = Guid.NewGuid(); + private int _authenticationTimeout = ADP.DefaultConnectionTimeout; + + /// + /// Implicitly converts to . + /// + public static implicit operator SqlAuthenticationParameters(SqlAuthenticationParametersBuilder builder) + { + return new SqlAuthenticationParameters( + authenticationMethod: builder._authenticationMethod, + serverName: builder._serverName, + databaseName: builder._databaseName, + resource: builder._resource, + authority: builder._authority, + userId: builder._userId, + password: builder._password, + connectionId: builder._connectionId, + connectionTimeout: builder._authenticationTimeout); + } + + /// + /// Set user id. + /// + public SqlAuthenticationParametersBuilder WithUserId(string userId) + { + _userId = userId; + return this; + } + + /// + /// Set password. + /// + public SqlAuthenticationParametersBuilder WithPassword(string password) + { + _password = password; + return this; + } + + /// + /// Set password. + /// + public SqlAuthenticationParametersBuilder WithPassword(SecureString password) + { + IntPtr valuePtr = IntPtr.Zero; + try + { + valuePtr = Marshal.SecureStringToGlobalAllocUnicode(password); + _password = Marshal.PtrToStringUni(valuePtr); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); + } + return this; + } + + /// + /// Set a specific connection id instead of using a random one. + /// + public SqlAuthenticationParametersBuilder WithConnectionId(Guid connectionId) + { + _connectionId = connectionId; + return this; + } + + /// + /// Set authentication timeout. + /// + public SqlAuthenticationParametersBuilder WithAuthenticationTimeout(int timeout) + { + _authenticationTimeout = timeout; + return this; + } + + internal SqlAuthenticationParametersBuilder(SqlAuthenticationMethod authenticationMethod, string resource, string authority, string serverName, string databaseName) + { + _authenticationMethod = authenticationMethod; + _serverName = serverName; + _databaseName = databaseName; + _resource = resource; + _authority = authority; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs deleted file mode 100644 index 25e1cf006e..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading.Tasks; - -namespace Microsoft.Data.SqlClient -{ - - /// - public abstract class SqlAuthenticationProvider - { - /// - public static SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) - { - return SqlAuthenticationProviderManager.Instance.GetProvider(authenticationMethod); - } - - /// - public static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) - { - return SqlAuthenticationProviderManager.Instance.SetProvider(authenticationMethod, provider); - } - - /// - public virtual void BeforeLoad(SqlAuthenticationMethod authenticationMethod) { } - - /// - public virtual void BeforeUnload(SqlAuthenticationMethod authenticationMethod) { } - - /// - public abstract bool IsSupported(SqlAuthenticationMethod authenticationMethod); - - /// - public abstract Task AcquireTokenAsync(SqlAuthenticationParameters parameters); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 447ea0e9c5..06d7423028 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -6,12 +6,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; +using System.IO; +using System.Reflection; + +#nullable enable namespace Microsoft.Data.SqlClient { - /// - /// Authentication provider manager. - /// internal sealed class SqlAuthenticationProviderManager { [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] @@ -27,7 +28,7 @@ internal sealed class SqlAuthenticationProviderManager static SqlAuthenticationProviderManager() { - SqlAuthenticationProviderConfigurationSection configurationSection = null; + SqlAuthenticationProviderConfigurationSection? configurationSection = null; try { @@ -46,49 +47,125 @@ static SqlAuthenticationProviderManager() } Instance = new SqlAuthenticationProviderManager(configurationSection); - SetDefaultAuthProviders(Instance); - } - /// - /// Sets default supported Active Directory Authentication providers by the driver - /// on the SqlAuthenticationProviderManager instance. - /// - private static void SetDefaultAuthProviders(SqlAuthenticationProviderManager instance) - { - if (instance != null) + // If our Azure extensions package is present, use its + // authentication provider as our default. + const string assemblyName = "Microsoft.Data.SqlClient.Extensions.Azure"; + + try { - var activeDirectoryAuthProvider = new ActiveDirectoryAuthenticationProvider(instance._applicationClientId); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, activeDirectoryAuthProvider); + // Try to load our Azure extension. + var assembly = Assembly.Load(assemblyName); + + if (assembly is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} not found; " + + "no default Active Directory provider installed"); + return; + } + + // TODO(ADO-39845): Verify the assembly is signed by us? + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} found; " + + "attempting to set as default provider for all Active " + + "Directory authentication methods"); + + // Look for the authentication provider class. + const string className = "Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationProvider"; + var type = assembly.GetType(className); + + if (type is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension does not contain class={className}; " + + "no default Active Directory provider installed"); + + return; + } + + // Try to instantiate it. + var instance = Activator.CreateInstance( + type, + [Instance._applicationClientId]) + as SqlAuthenticationProvider; + + if (instance is null) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Failed to instantiate Azure extension class={className}; " + + "no default Active Directory provider installed"); + + return; + } + + // We successfully instantiated the provider, so set it as the + // default for all Active Directory authentication methods. + // + // Note that SetProvider() will refuse to clobber an application + // specified provider, so these defaults will only be applied + // for methods that do not already have a provider. + SetProvider(SqlAuthenticationMethod.ActiveDirectoryIntegrated, instance); #pragma warning disable 0618 // Type or member is obsolete - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, activeDirectoryAuthProvider); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, instance); #pragma warning restore 0618 // Type or member is obsolete - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, activeDirectoryAuthProvider); - instance.SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, activeDirectoryAuthProvider); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryMSI, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryDefault, instance); + SetProvider(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, instance); + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension class={className} installed as " + + "provider for all Active Directory authentication methods"); } + // All of these exceptions mean we couldn't find or instantiate the + // Azure extension's authentication provider, in which case we + // simply have no default and the app must provide one if they + // attempt to use Active Directory authentication. + catch (Exception ex) + when (ex is ArgumentNullException || + ex is ArgumentException || + ex is BadImageFormatException || + ex is FileLoadException || + ex is FileNotFoundException || + ex is MemberAccessException || + ex is MethodAccessException || + ex is MissingMethodException || + ex is NotSupportedException || + ex is TargetInvocationException || + ex is TypeLoadException) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assemblyName} not found or " + + "not usable; no default provider installed; " + + $"{ex.GetType().Name}: {ex.Message}"); + } + // Any other exceptions are fatal. } - public static readonly SqlAuthenticationProviderManager Instance; + private static readonly SqlAuthenticationProviderManager Instance; - private readonly SqlAuthenticationInitializer _initializer; - private readonly IReadOnlyCollection _authenticationsWithAppSpecifiedProvider; - private readonly ConcurrentDictionary _providers; + private readonly HashSet _authenticationsWithAppSpecifiedProvider = new(); + private readonly ConcurrentDictionary _providers = new(); private readonly SqlClientLogger _sqlAuthLogger = new SqlClientLogger(); - private readonly string _applicationClientId = ActiveDirectoryAuthentication.AdoClientId; + private readonly string? _applicationClientId = null; /// /// Constructor. /// - public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection configSection = null) + private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSection? configSection) { var methodName = "Ctor"; - _providers = new ConcurrentDictionary(); - var authenticationsWithAppSpecifiedProvider = new HashSet(); - _authenticationsWithAppSpecifiedProvider = authenticationsWithAppSpecifiedProvider; if (configSection == null) { @@ -112,8 +189,14 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe try { var initializerType = Type.GetType(configSection.InitializerType, true); - _initializer = (SqlAuthenticationInitializer)Activator.CreateInstance(initializerType); - _initializer.Initialize(); + if (initializerType is not null) + { + var initializer = (SqlAuthenticationInitializer?)Activator.CreateInstance(initializerType); + if (initializer is not null) + { + initializer.Initialize(); + } + } } catch (Exception e) { @@ -132,23 +215,31 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe foreach (ProviderSettings providerSettings in configSection.Providers) { SqlAuthenticationMethod authentication = AuthenticationEnumFromString(providerSettings.Name); - SqlAuthenticationProvider provider; + SqlAuthenticationProvider? provider; try { var providerType = Type.GetType(providerSettings.Type, true); - provider = (SqlAuthenticationProvider)Activator.CreateInstance(providerType); + if (providerType is null) + { + continue; + } + provider = (SqlAuthenticationProvider?)Activator.CreateInstance(providerType); } catch (Exception e) { throw SQL.CannotCreateAuthProvider(authentication.ToString(), providerSettings.Type, e); } + if (provider is null) + { + continue; + } if (!provider.IsSupported(authentication)) { throw SQL.UnsupportedAuthenticationByProvider(authentication.ToString(), providerSettings.Type); } _providers[authentication] = provider; - authenticationsWithAppSpecifiedProvider.Add(authentication); + _authenticationsWithAppSpecifiedProvider.Add(authentication); _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, string.Format("Added user-defined auth provider: {0} for authentication {1}.", providerSettings?.Type, authentication)); } } @@ -158,54 +249,48 @@ public SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationSe } } - /// - /// Get an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider or null if not found. - public SqlAuthenticationProvider GetProvider(SqlAuthenticationMethod authenticationMethod) + internal static SqlAuthenticationProvider? GetProvider(SqlAuthenticationMethod authenticationMethod) { - SqlAuthenticationProvider value; - return _providers.TryGetValue(authenticationMethod, out value) ? value : null; + SqlAuthenticationProvider? value; + return Instance._providers.TryGetValue(authenticationMethod, out value) ? value : null; } - /// - /// Set an authentication provider by method. - /// - /// Authentication method. - /// Authentication provider. - /// True if succeeded, false otherwise, e.g., the existing provider disallows overriding. - public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) + internal static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) { if (!provider.IsSupported(authenticationMethod)) { throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); } var methodName = "SetProvider"; - if (_authenticationsWithAppSpecifiedProvider.Count > 0) + if (Instance._authenticationsWithAppSpecifiedProvider.Count > 0) { - foreach (SqlAuthenticationMethod candidateMethod in _authenticationsWithAppSpecifiedProvider) + foreach (SqlAuthenticationMethod candidateMethod in Instance._authenticationsWithAppSpecifiedProvider) { if (candidateMethod == authenticationMethod) { - _sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(_providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); - return false; // return here to avoid replacing user-defined provider + Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); + + // The app has already specified a Provider for this + // authentication method, so we won't override it. + return false; } } } - _providers.AddOrUpdate(authenticationMethod, provider, (key, oldProvider) => - { - if (oldProvider != null) - { - oldProvider.BeforeUnload(authenticationMethod); - } - if (provider != null) + Instance._providers.AddOrUpdate( + authenticationMethod, + provider, + (SqlAuthenticationMethod key, SqlAuthenticationProvider oldProvider) => { + if (oldProvider != null) + { + oldProvider.BeforeUnload(authenticationMethod); + } + provider.BeforeLoad(authenticationMethod); - } - _sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); - return provider; - }); + + Instance._sqlAuthLogger.LogInfo(nameof(SqlAuthenticationProviderManager), methodName, $"Added auth provider {GetProviderType(provider)}, overriding existed provider {GetProviderType(oldProvider)} for authentication {authenticationMethod}."); + return provider; + }); return true; } @@ -216,7 +301,7 @@ public bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthent /// /// /// - private static T FetchConfigurationSection(string name) + private static T? FetchConfigurationSection(string name) where T : class { Type t = typeof(T); @@ -265,14 +350,13 @@ private static SqlAuthenticationMethod AuthenticationEnumFromString(string authe } } - private static string GetProviderType(SqlAuthenticationProvider provider) + private static string GetProviderType(SqlAuthenticationProvider? provider) { - if (provider == null) + if (provider is null) { return "null"; } - - return provider.GetType().FullName; + return provider.GetType().FullName ?? "unknown"; } } @@ -293,13 +377,13 @@ internal class SqlAuthenticationProviderConfigurationSection : ConfigurationSect /// User-defined initializer. /// [ConfigurationProperty("initializerType")] - public string InitializerType => this["initializerType"] as string; + public string InitializerType => this["initializerType"] as string ?? string.Empty; /// /// Application Client Id /// [ConfigurationProperty("applicationClientId", IsRequired = false)] - public string ApplicationClientId => this["applicationClientId"] as string; + public string ApplicationClientId => this["applicationClientId"] as string ?? string.Empty; } /// diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs deleted file mode 100644 index d61ab75008..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationToken.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Text; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlAuthenticationToken - { - /// - public DateTimeOffset ExpiresOn { get; } - - /// - public string AccessToken { get; } - - /// - public SqlAuthenticationToken(string accessToken, DateTimeOffset expiresOn) - { - if (string.IsNullOrEmpty(accessToken)) - { - throw SQL.ParameterCannotBeEmpty("AccessToken"); - } - - AccessToken = accessToken; - ExpiresOn = expiresOn; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index 8931f445be..4183515fe7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -1125,44 +1125,6 @@ public enum SqlCommandColumnEncryptionSetting /// Disabled, } - - /// - public enum SqlAuthenticationMethod - { - /// - NotSpecified = 0, - - /// - SqlPassword, - - /// - [Obsolete("ActiveDirectoryPassword is deprecated, use a more secure authentication method. See https://aka.ms/SqlClientEntraIDAuthentication for more details.")] - ActiveDirectoryPassword, - - /// - ActiveDirectoryIntegrated, - - /// - ActiveDirectoryInteractive, - - /// - ActiveDirectoryServicePrincipal, - - /// - ActiveDirectoryDeviceCodeFlow, - - /// - ActiveDirectoryManagedIdentity, - - /// - ActiveDirectoryMSI, - - /// - ActiveDirectoryDefault, - - /// - ActiveDirectoryWorkloadIdentity - } // This enum indicates the state of TransparentNetworkIPResolution // The first attempt when TNIR is on should be sequential. If the first attempt fails next attempts should be parallel. internal enum TransparentNetworkResolutionState @@ -1172,12 +1134,6 @@ internal enum TransparentNetworkResolutionState ParallelMode }; - internal class ActiveDirectoryAuthentication - { - internal const string AdoClientId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; - internal const string MSALGetAccessTokenFunctionName = "AcquireToken"; - } - // Fields in the first resultset of "sp_describe_parameter_encryption". // We expect the server to return the fields in the resultset in the same order as mentioned below. // If the server changes the below order, then transparent parameter encryption will break. diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index f354e6f806..d86b6684d1 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -49,50 +49,29 @@ private void InvalidCombinationCheck(SqlCredential credential) Assert.Throws(() => connection.AccessToken = "SampleAccessToken"); } } - - #if NETFRAMEWORK - // This test is only valid for .NET Framework /// - /// Tests whether SQL Auth provider is overridden using app.config file. - /// This use case is only supported for .NET Framework applications, as driver doesn't support reading configuration from appsettings.json file. - /// In future if need be, appsettings.json support can be added. + /// Tests whether a dummy SQL Auth provider is registered due to + /// configuration in an app.config file. Only .NET Framework reads + /// from the app.config file, so this test is only valid for that + /// runtime. + /// + /// See the app.config file in the same directory as this file. + /// + /// .NET (Core) reads similar configuration from appsettings.json, but + /// our SqlAuthenticationProviderManager does not currently support + /// that configuration source. /// - [Fact] + [ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] public async Task IsDummySqlAuthenticationProviderSetByDefault() { var provider = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryInteractive); Assert.NotNull(provider); - Assert.Equal(typeof(DummySqlAuthenticationProvider), provider.GetType()); + Assert.IsType(provider); var token = await provider.AcquireTokenAsync(null); Assert.Equal(token.AccessToken, DummySqlAuthenticationProvider.DUMMY_TOKEN_STR); } - #endif - - [Fact] - public void CustomActiveDirectoryProviderTest() - { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); - } - - [Fact] - public void CustomActiveDirectoryProviderTest_AppClientId() - { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(Guid.NewGuid().ToString()); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); - } - - [Fact] - public void CustomActiveDirectoryProviderTest_AppClientId_DeviceFlowCallback() - { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask, Guid.NewGuid().ToString()); - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); - Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs new file mode 100644 index 0000000000..ecc9d8fa0f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Data.SqlClient.FunctionalTests.DataCommon; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + public class SqlAuthenticationProviderManagerTests + { + // The FunctionalTests project employs a .NET Framework app.config file + // that configures a dummy authentication provider for + // ActiveDirectoryInteractive authentication. Verify that this is + // respected. + [ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] + public void DefaultAuthenticationProviders_AppConfig() + { + // The provider for ActiveDirectoryInteractive should be our dummy + // provider. + Assert.IsType( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + + // There should be no provider for other methods. Spot-check a few. + Assert.Null(SqlAuthenticationProvider.GetProvider( + #pragma warning disable CS0618 // Type or member is obsolete + SqlAuthenticationMethod.ActiveDirectoryPassword)); + #pragma warning restore CS0618 // Type or member is obsolete + + Assert.Null(SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs deleted file mode 100644 index 8168c26f8e..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Data.SqlClient.FunctionalTests.DataCommon; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests -{ - public class SqlAuthenticationProviderTest - { - [Theory] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryIntegrated)] - #pragma warning disable 0618 // Type or member is obsolete - [InlineData(SqlAuthenticationMethod.ActiveDirectoryPassword)] - #pragma warning restore 0618 // Type or member is obsolete - [InlineData(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryMSI)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryDefault)] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity)] - public void DefaultAuthenticationProviders(SqlAuthenticationMethod method) - { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); - } - - #if NETFRAMEWORK - // This test is only valid for .NET Framework - - // Overridden by app.config in this project - [Theory] - [InlineData(SqlAuthenticationMethod.ActiveDirectoryInteractive)] - public void DefaultAuthenticationProviders_Interactive(SqlAuthenticationMethod method) - { - Assert.IsType(SqlAuthenticationProvider.GetProvider(method)); - } - - #endif - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 0bb9654efc..a62bb9787b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -239,6 +239,15 @@ static DataTestUtility() AEConnStringsSetup.Add(TCPConnectionString); } } + + // Many of our tests require a Managed Identity provider to be + // registered. + // + // TODO: Figure out which ones and install on-demand rather than + // globally. + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, + new ManagedIdentityProvider()); } public static IEnumerable ConnectionStrings => GetConnectionStrings(withEnclave: true); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs new file mode 100644 index 0000000000..0e590be0f9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ManagedIdentityProvider.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +using Azure.Core; +using Azure.Identity; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests; + +internal class ManagedIdentityProvider : SqlAuthenticationProvider +{ + // Our cache of managed identity user Ids to credential instances. + private readonly ConcurrentDictionary + _credentialCache = new(); + + // The default suffix to apply to resource scopes. + private const string s_defaultScopeSuffix = "/.default"; + + // Acquire a token using Managed Identity. The UserId in the parameters is + // used as the managed identity client ID. Tokens are cached per UserId. + // + // GOTCHA: This assumes that the Resource and Authority in the parameters + // never change for a given UserId, which is probably a safe assumption for + // tests. + // + public override async Task AcquireTokenAsync( + SqlAuthenticationParameters parameters) + { + if (parameters.UserId is null) + { + throw new TokenException( + "Refusing to acquire token for ManagedIdentity with null UserId"); + } + + try + { + // Build an appropriate scope. + string scope = parameters.Resource.EndsWith( + s_defaultScopeSuffix, StringComparison.Ordinal) + ? parameters.Resource + : parameters.Resource + s_defaultScopeSuffix; + + TokenRequestContext context = new([scope]); + + TokenCredentialOptions options = new() + { + AuthorityHost = new Uri(parameters.Authority) + }; + + // Create or re-use the ManagedIdentityCredential for this UserId. + ManagedIdentityCredential credential = + _credentialCache.GetOrAdd( + parameters.UserId, + (_) => new(parameters.UserId, options)); + + // Set up a cancellation token based on the authentication timeout, + // ignoring overflow since this is just test code. + using CancellationTokenSource cancellor = new(); + cancellor.CancelAfter(parameters.ConnectionTimeout * 1000); + + // Acquire the token, which may be cached by the credential. + AccessToken token = + await credential.GetTokenAsync(context, cancellor.Token) + .ConfigureAwait(false); + + return new(token.Token, token.ExpiresOn); + } + catch (Exception ex) + { + throw new TokenException( + $"Failed to acquire token for ManagedIdentity " + + $"userId ={parameters.UserId} error={ex.Message}", ex); + } + } + + /// We support only the Managed Identity authentication method. + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; + } + + // The exception we throw on any errors acquiring tokens. + private sealed class TokenException : SqlAuthenticationProviderException + { + internal TokenException(string message, Exception? causedBy = null) + : base(message, causedBy) + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs new file mode 100644 index 0000000000..5f9ca1eff0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/UsernamePasswordProvider.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests; + +internal class UsernamePasswordProvider : SqlAuthenticationProvider +{ + readonly string _appClientId; + const string s_defaultScopeSuffix = "/.default"; + + internal UsernamePasswordProvider(string appClientId) + { + _appClientId = appClientId; + } + + public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) + { + try + { + string scope = + parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) + ? parameters.Resource + : parameters.Resource + s_defaultScopeSuffix; + + using CancellationTokenSource cts = new(); + cts.CancelAfter(parameters.ConnectionTimeout * 1000); + + AuthenticationResult result = + #pragma warning disable CS0618 // Type or member is obsolete + await PublicClientApplicationBuilder.Create(_appClientId) + .WithAuthority(parameters.Authority) + .Build() + .AcquireTokenByUsernamePassword([scope], parameters.UserId, parameters.Password) + #pragma warning restore CS0618 // Type or member is obsolete + .WithCorrelationId(parameters.ConnectionId) + .ExecuteAsync(cancellationToken: cts.Token); + + return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); + } + catch (Exception ex) + { + throw new TokenException( + $"Failed to acquire token for username/password " + + $"userId ={parameters.UserId} error={ex.Message}", ex); + } + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + #pragma warning disable 0618 // Type or member is obsolete + return authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete + } + + // The exception we throw on any errors acquiring tokens. + private sealed class TokenException : SqlAuthenticationProviderException + { + internal TokenException(string message, Exception? causedBy = null) + : base(message, causedBy) + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 3155b87f81..54cf74b7a0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -272,9 +272,11 @@ + + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs index 54900a4dd0..e99192732d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs @@ -22,10 +22,19 @@ public AADFedAuthTokenRefreshTest(ITestOutputHelper testOutputHelper) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADPasswordConnStrSetup))] public void FedAuthTokenRefreshTest() { - string connectionString = DataTestUtility.AADPasswordConnectionString; + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete - using (SqlConnection connection = new SqlConnection(connectionString)) + try { + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete + + string connectionString = DataTestUtility.AADPasswordConnectionString; + + using SqlConnection connection = new SqlConnection(connectionString); connection.Open(); string oldTokenHash = ""; @@ -65,6 +74,16 @@ public void FedAuthTokenRefreshTest() Assert.True(newLocalExpiryTime > oldLocalExpiryTime, "The refreshed token must have a new or later expiry time."); } } + finally + { + if (original is not null) + { + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete + } + } } [Conditional("DEBUG")] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 608c34c977..f2c0128c46 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -5,60 +5,14 @@ using System; using System.Diagnostics; using System.Security; -using System.Threading; using System.Threading.Tasks; using Azure.Core; -using Azure.Identity; -using Microsoft.Identity.Client; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - public class AADConnectionsTest + public class AADConnectionTest { - class CustomSqlAuthenticationProvider : SqlAuthenticationProvider - { - string _appClientId; - - internal CustomSqlAuthenticationProvider(string appClientId) - { - _appClientId = appClientId; - } - - public override async Task AcquireTokenAsync(SqlAuthenticationParameters parameters) - { - string s_defaultScopeSuffix = "/.default"; - string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix, StringComparison.Ordinal) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix; - - _ = parameters.ServerName; - _ = parameters.DatabaseName; - _ = parameters.ConnectionId; - - var cts = new CancellationTokenSource(); - cts.CancelAfter(parameters.ConnectionTimeout * 1000); - - string[] scopes = new string[] { scope }; - SecureString password = new SecureString(); - -#pragma warning disable CS0618 // Type or member is obsolete - AuthenticationResult result = await PublicClientApplicationBuilder.Create(_appClientId) - .WithAuthority(parameters.Authority) - .Build().AcquireTokenByUsernamePassword(scopes, parameters.UserId, parameters.Password) - .WithCorrelationId(parameters.ConnectionId) - .ExecuteAsync(cancellationToken: cts.Token); -#pragma warning restore CS0618 // Type or member is obsolete - - return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn); - } - - public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) - { - #pragma warning disable 0618 // Type or member is obsolete - return authenticationMethod.Equals(SqlAuthenticationMethod.ActiveDirectoryPassword); - #pragma warning restore 0618 // Type or member is obsolete - } - } - private static void ConnectAndDisconnect(string connectionString, SqlCredential credential = null) { using (SqlConnection conn = new SqlConnection(connectionString)) @@ -80,15 +34,6 @@ private static void ConnectAndDisconnect(string connectionString, SqlCredential private static bool IsManagedIdentitySetup() => DataTestUtility.ManagedIdentitySupported; private static bool SupportsSystemAssignedManagedIdentity() => DataTestUtility.SupportsSystemAssignedManagedIdentity; - [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void KustoDatabaseTest() - { - // This is a sample Kusto database that can be connected by any AD account. - using SqlConnection connection = new SqlConnection($"Data Source=help.kusto.windows.net; Authentication=Active Directory Default;Trust Server Certificate=True;User ID = {DataTestUtility.UserManagedIdentityClientId};"); - connection.Open(); - Assert.True(connection.State == System.Data.ConnectionState.Open); - } [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup))] public static void AccessTokenTest() @@ -214,47 +159,33 @@ public static void AADPasswordWithIntegratedSecurityTrue() Assert.Contains(expectedMessage, e.Message); } - [ConditionalFact(nameof(IsAccessTokenSetup), nameof(IsAADConnStringsSetup))] - public static void AADPasswordWithWrongPassword() - { - string[] credKeys = { "Password", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + "Password=TestPassword;"; - - Assert.Throws(() => ConnectAndDisconnect(connStr)); - - // We cannot verify error message with certainity as driver may cache token from other tests for current user - // and error message may change accordingly. - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void GetAccessTokenByPasswordTest() { - // Clear token cache for code coverage. - ActiveDirectoryAuthenticationProvider.ClearUserTokenCache(); - using (SqlConnection connection = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete + + try { - connection.Open(); - Assert.True(connection.State == System.Data.ConnectionState.Open); - } - } + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void TestADPasswordAuthentication() - { - // Connect to Azure DB with password and retrieve user name. - using (SqlConnection conn = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + using (SqlConnection connection = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + { + connection.Open(); + Assert.True(connection.State == System.Data.ConnectionState.Open); + } + } + finally { - conn.Open(); - using (SqlCommand sqlCommand = new SqlCommand - ( - cmdText: $"SELECT SUSER_SNAME();", - connection: conn, - transaction: null - )) + if (original is not null) { - string customerId = (string)sqlCommand.ExecuteScalar(); - string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); - Assert.Equal(expected, customerId); + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } } @@ -263,28 +194,41 @@ public static void TestADPasswordAuthentication() public static void TestCustomProviderAuthentication() { #pragma warning disable 0618 // Type or member is obsolete - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new CustomSqlAuthenticationProvider(DataTestUtility.ApplicationClientId)); + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); #pragma warning restore 0618 // Type or member is obsolete - // Connect to Azure DB with password and retrieve user name using custom authentication provider - using (SqlConnection conn = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) + + try { - conn.Open(); - using (SqlCommand sqlCommand = new SqlCommand - ( - cmdText: $"SELECT SUSER_SNAME();", - connection: conn, - transaction: null - )) + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete + // Connect to Azure DB with password and retrieve user name using custom authentication provider + using (SqlConnection conn = new SqlConnection(DataTestUtility.AADPasswordConnectionString)) { - string customerId = (string)sqlCommand.ExecuteScalar(); - string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); - Assert.Equal(expected, customerId); + conn.Open(); + using (SqlCommand sqlCommand = new SqlCommand + ( + cmdText: $"SELECT SUSER_SNAME();", + connection: conn, + transaction: null + )) + { + string customerId = (string)sqlCommand.ExecuteScalar(); + string expected = DataTestUtility.RetrieveValueFromConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User ID", "UID" }); + Assert.Equal(expected, customerId); + } + } + } + finally + { + if (original is not null) + { + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } - // Reset to driver internal provider. - #pragma warning disable 0618 // Type or member is obsolete - SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new ActiveDirectoryAuthenticationProvider(DataTestUtility.ApplicationClientId)); - #pragma warning restore 0618 // Type or member is obsolete } [ConditionalFact(nameof(IsAADConnStringsSetup))] @@ -321,92 +265,6 @@ public static void MFAAuthWithPassword() Assert.Contains(expectedMessage, e.Message); } - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyPasswordInConnStrAADPassword() - { - // connection fails with expected error message. - string[] pwdKey = { "Password", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, pwdKey) + "Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string user = DataTestUtility.FetchKeyInConnStr(DataTestUtility.AADPasswordConnectionString, new string[] { "User Id", "UID" }); - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyCredInConnStrAADPassword() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "Failed to authenticate the user in Active Directory (Authentication=ActiveDirectoryPassword)."; - Assert.Contains(expectedMessage, e.Message); - } - - [PlatformSpecific(TestPlatforms.AnyUnix)] - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void EmptyCredInConnStrAADPasswordAnyUnix() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + "User ID=; Password=;"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = "MSAL cannot determine the username (UPN) of the currently logged in user.For Integrated Windows Authentication and Username/Password flows, please use .WithUsername() before calling ExecuteAsync()."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void AADPasswordWithInvalidUser() - { - // connection fails with expected error message. - string[] removeKeys = { "User ID", "UID" }; - string user = "testdotnet@domain.com"; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + $"User ID={user}"; - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStr)); - - string expectedMessage = string.Format("Failed to authenticate the user {0} in Active Directory (Authentication=ActiveDirectoryPassword).", user); - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup))] - public static void NoCredentialsActiveDirectoryPassword() - { - // test Passes with correct connection string. - ConnectAndDisconnect(DataTestUtility.AADPasswordConnectionString); - - // connection fails with expected error message. - string[] credKeys = { "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys); - InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Password'."; - Assert.Contains(expectedMessage, e.Message); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADServicePrincipalSetup))] - public static void NoCredentialsActiveDirectoryServicePrincipal() - { - // test Passes with correct connection string. - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Service Principal; User ID={DataTestUtility.AADServicePrincipalId}; PWD={DataTestUtility.AADServicePrincipalSecret};"; - ConnectAndDisconnect(connStr); - - // connection fails with expected error message. - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - "Authentication=Active Directory Service Principal;"; - InvalidOperationException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "Either Credential or both 'User ID' and 'Password' (or 'UID' and 'PWD') connection string keywords must be specified, if 'Authentication=Active Directory Service Principal'."; - Assert.Contains(expectedMessage, e.Message); - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ActiveDirectoryDeviceCodeFlowWithUserIdMustFail() { @@ -497,22 +355,6 @@ public static void ActiveDirectoryManagedIdentityWithPasswordMustFail() Assert.Contains(expectedMessage, e.Message); } - [InlineData("2445343 2343253")] - [InlineData("2445343$#^@@%2343253")] - [ConditionalTheory(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void ActiveDirectoryManagedIdentityWithInvalidUserIdMustFail(string userId) - { - // connection fails with expected error message. - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStrWithNoCred = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - $"Authentication=Active Directory Managed Identity; User Id={userId}"; - - SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - - string expectedMessage = "[Managed Identity] Authentication unavailable"; - Assert.Contains(expectedMessage, e.GetBaseException().Message, StringComparison.OrdinalIgnoreCase); - } - [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ActiveDirectoryMSIWithCredentialsMustFail() { @@ -654,85 +496,66 @@ public static void AccessTokenCallbackReceivesUsernameAndPassword() } } - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void ActiveDirectoryDefaultMustPass() - { - string[] credKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, credKeys) + - $"Authentication=ActiveDirectoryDefault;User ID={DataTestUtility.UserManagedIdentityClientId};"; - - // Connection should be established using Managed Identity by default. - ConnectAndDisconnect(connStr); - } - - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsIntegratedSecuritySetup), nameof(DataTestUtility.AreConnStringsSetup))] - public static void ADIntegratedUsingSSPI() - { - // test Passes with correct connection string. - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) + - $"Authentication=Active Directory Integrated;"; - ConnectAndDisconnect(connStr); - } - // Test passes locally everytime, but in pieplines fails randomly with uncertainity. // e.g. Second AAD connection too slow (802ms)! (More than 30% of the first (576ms).) [ActiveIssue("16058")] [ConditionalFact(nameof(IsAADConnStringsSetup))] public static void ConnectionSpeed() { - var connString = DataTestUtility.AADPasswordConnectionString; + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider original = SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword); + #pragma warning restore 0618 // Type or member is obsolete - //Ensure server endpoints are warm - using (var connectionDrill = new SqlConnection(connString)) + try { - connectionDrill.Open(); - } + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, new UsernamePasswordProvider(DataTestUtility.ApplicationClientId)); + #pragma warning restore 0618 // Type or member is obsolete - SqlConnection.ClearAllPools(); - ActiveDirectoryAuthenticationProvider.ClearUserTokenCache(); + var connString = DataTestUtility.AADPasswordConnectionString; - Stopwatch firstConnectionTime = new Stopwatch(); - Stopwatch secondConnectionTime = new Stopwatch(); + //Ensure server endpoints are warm + using (var connectionDrill = new SqlConnection(connString)) + { + connectionDrill.Open(); + } + + SqlConnection.ClearAllPools(); - using (var connectionDrill = new SqlConnection(connString)) + Stopwatch firstConnectionTime = new Stopwatch(); + Stopwatch secondConnectionTime = new Stopwatch(); + + using (var connectionDrill = new SqlConnection(connString)) + { + firstConnectionTime.Start(); + connectionDrill.Open(); + firstConnectionTime.Stop(); + using (var connectionDrill2 = new SqlConnection(connString)) + { + secondConnectionTime.Start(); + connectionDrill2.Open(); + secondConnectionTime.Stop(); + } + } + + // Subsequent AAD connections within a short timeframe should use an auth token cached from the connection pool + // Second connection speed in tests was typically 10-15% of the first connection time. Using 30% since speeds may vary. + Assert.True(((double)secondConnectionTime.ElapsedMilliseconds / firstConnectionTime.ElapsedMilliseconds) < 0.30, $"Second AAD connection too slow ({secondConnectionTime.ElapsedMilliseconds}ms)! (More than 30% of the first ({firstConnectionTime.ElapsedMilliseconds}ms).)"); + } + finally { - firstConnectionTime.Start(); - connectionDrill.Open(); - firstConnectionTime.Stop(); - using (var connectionDrill2 = new SqlConnection(connString)) + if (original is not null) { - secondConnectionTime.Start(); - connectionDrill2.Open(); - secondConnectionTime.Stop(); + // Reset to driver internal provider. + #pragma warning disable 0618 // Type or member is obsolete + SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryPassword, original); + #pragma warning restore 0618 // Type or member is obsolete } } - - // Subsequent AAD connections within a short timeframe should use an auth token cached from the connection pool - // Second connection speed in tests was typically 10-15% of the first connection time. Using 30% since speeds may vary. - Assert.True(((double)secondConnectionTime.ElapsedMilliseconds / firstConnectionTime.ElapsedMilliseconds) < 0.30, $"Second AAD connection too slow ({secondConnectionTime.ElapsedMilliseconds}ms)! (More than 30% of the first ({firstConnectionTime.ElapsedMilliseconds}ms).)"); } #region Managed Identity Authentication tests - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] - public static void SystemAssigned_ManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Managed Identity;"; - ConnectAndDisconnect(connStr); - } - - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup))] - public static void UserAssigned_ManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD" }; - string connStr = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.AADPasswordConnectionString, removeKeys) + - $"Authentication=Active Directory Managed Identity; User Id={DataTestUtility.UserManagedIdentityClientId};"; - ConnectAndDisconnect(connStr); - } - [ConditionalFact(nameof(IsAADConnStringsSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void AccessToken_SystemManagedIdentityTest() { @@ -761,36 +584,6 @@ public static void AccessToken_UserManagedIdentityTest() } } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] - public static void Azure_SystemManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connectionString = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) - + $"Authentication=Active Directory Managed Identity;"; - - using (SqlConnection conn = new SqlConnection(connectionString)) - { - conn.Open(); - - Assert.True(conn.State == System.Data.ConnectionState.Open); - } - } - - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsManagedIdentitySetup))] - public static void Azure_UserManagedIdentityTest() - { - string[] removeKeys = { "Authentication", "User ID", "Password", "UID", "PWD", "Trusted_Connection", "Integrated Security" }; - string connectionString = DataTestUtility.RemoveKeysInConnStr(DataTestUtility.TCPConnectionString, removeKeys) - + $"Authentication=Active Directory Managed Identity; User Id={DataTestUtility.UserManagedIdentityClientId}"; - - using (SqlConnection conn = new SqlConnection(connectionString)) - { - conn.Open(); - - Assert.True(conn.State == System.Data.ConnectionState.Open); - } - } - [ConditionalFact(nameof(AreConnStringsSetup), nameof(IsAzure), nameof(IsAccessTokenSetup), nameof(IsManagedIdentitySetup), nameof(SupportsSystemAssignedManagedIdentity))] public static void Azure_AccessToken_SystemManagedIdentityTest() { diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props index f547c6aa98..4e0e64326d 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/Directory.Packages.props @@ -13,7 +13,7 @@ true true - + + + + + $(AzurePackageVersion) + + + + + 1.0.0 + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj index 91fe0f81ee..ba086b58a8 100644 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj +++ b/src/Microsoft.Data.SqlClient/tests/StressTests/SqlClient.Stress.Framework/SqlClient.Stress.Framework.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs new file mode 100644 index 0000000000..5409b0f7e4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.Test.UnitTests; + +public class SqlAuthenticationProviderManagerTests +{ + private class Provider : SqlAuthenticationProvider + { + public override Task AcquireTokenAsync( + SqlAuthenticationParameters parameters) + { + return Task.FromResult( + new SqlAuthenticationToken( + "SampleAccessToken", DateTimeOffset.UtcNow.AddMinutes(5))); + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; + } + } + + // Verify that we can get and set providers via both the Abstractions + // package and Manager class interchangeably. + // + // This tests the dynamic assembly loading code in the Abstractions + // package. + [Fact] + public void Abstractions_And_Manager_GetSetProvider_Equivalent() + { + // Set via Manager, get via both. + Provider provider1 = new(); + + Assert.True( + SqlAuthenticationProviderManager.SetProvider( + // GOTCHA: On .NET Framework, the dummy provider is already + // registered as the default provider for Interactive, so we + // use DeviceCodeFlow instead. + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, + provider1)); + + Assert.Same( + provider1, + SqlAuthenticationProviderManager.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + + Assert.Same( + provider1, + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + + // Set via Abstractions, get via both. + Provider provider2 = new(); + + Assert.True( + SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, + provider2)); + + Assert.Same( + provider2, + SqlAuthenticationProviderManager.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + + Assert.Same( + provider2, + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); + } +} diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index bec49cb1ef..079e16d41c 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -27,11 +27,19 @@ https://go.microsoft.com/fwlink/?linkid=2090501 © Microsoft Corporation. All rights reserved. sqlclient microsoft.data.sqlclient + + + + @@ -49,6 +57,7 @@ + @@ -61,6 +70,7 @@ + @@ -73,6 +83,7 @@ + From bf0b91a91fb8c469fe7ad76031f76c9ab4c0056f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:37:23 -0400 Subject: [PATCH 02/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Brought over the pipeline changes. - Removed unnecessary flexibility from the MDS official pipeline tree. --- build.proj | 10 +++ .../jobs/build-signed-package-job.yml | 34 ++++---- .../templates/jobs/ci-build-nugets-job.yml | 33 ++++++-- .../templates/jobs/ci-run-tests-job.yml | 15 +++- .../jobs/run-tests-package-reference-job.yml | 12 ++- .../templates/stages/ci-run-tests-stage.yml | 26 +++++- ...ld-all-configurations-signed-dlls-step.yml | 30 ++++--- .../templates/steps/build-all-tests-step.yml | 10 ++- .../build-and-run-tests-netcore-step.yml | 58 +++++++------- .../steps/build-and-run-tests-netfx-step.yml | 58 +++++++------- .../templates/steps/ci-project-build-step.yml | 23 ++++-- .../steps/copy-dlls-for-test-step.yml | 80 +++++++------------ .../templates/steps/publish-symbols-step.yml | 72 ++++++----------- .../templates/steps/run-all-tests-step.yml | 39 ++++++--- eng/pipelines/dotnet-sqlclient-ci-core.yml | 35 +++++--- .../stages/stress-tests-ci-stage.yml | 13 ++- 16 files changed, 303 insertions(+), 245 deletions(-) diff --git a/build.proj b/build.proj index ef46c58cf5..a2026334f0 100644 --- a/build.proj +++ b/build.proj @@ -289,6 +289,7 @@ $(DotnetPath)dotnet test "@(UnitTestsProj)" -f $(TF) -p:Configuration=$(Configuration) + -p:ReferenceType=Project $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -311,6 +312,7 @@ $(DotnetPath)dotnet test "@(UnitTestsProj)" -f $(TF) -p:Configuration=$(Configuration) + -p:ReferenceType=Project $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -337,6 +339,8 @@ -f $(TF) -p:Configuration=$(Configuration) -p:ReferenceType=$(ReferenceType) + -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) + -p:MdsPackageVersion=$(MdsPackageVersion) $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -360,6 +364,8 @@ -f $(TF) -p:Configuration=$(Configuration) -p:ReferenceType=$(ReferenceType) + -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) + -p:MdsPackageVersion=$(MdsPackageVersion) $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -390,6 +396,8 @@ -p:ReferenceType=$(ReferenceType) -p:TestSet=$(TestSet) -p:TestTargetOS=Windows$(TargetGroup) + -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) + -p:MdsPackageVersion=$(MdsPackageVersion) $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -416,6 +424,8 @@ -p:ReferenceType=$(ReferenceType) -p:TestSet=$(TestSet) -p:TestTargetOS=Unixnetcoreapp + -p:AbstractionsPackageVersion=$(AbstractionsPackageVersion) + -p:MdsPackageVersion=$(MdsPackageVersion) $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 0061a2874f..9e05c706e0 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -23,10 +23,12 @@ jobs: displayName: 'Build Signed MDS Package' pool: type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - + variables: - template: /eng/pipelines/libraries/variables.yml@self - ${{ if parameters.isPreview }}: + - name: abstractionsPackageVersion + value: $(abstractionsPackagePreviewVersion) - name: mdsPackageVersion value: $(previewMdsPackageVersion) @@ -44,25 +46,25 @@ jobs: displayName: 'Build Tooling' inputs: solution: '**/build.proj' - configuration: $(Configuration) + configuration: Release msbuildArguments: -t:BuildTools # Perform analysis before building, since this step will clobber build output. - template: /eng/pipelines/common/templates/steps/code-analyze-step.yml@self - - # Build MDS in Package mode, producing signed DLLs. + + # Build MDS, producing signed DLLs. - template: /eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml@self parameters: - buildConfiguration: $(Configuration) # These variables are sourced from common-variables.yml. + abstractionsAssemblyFileVersion: $(abstractionsAssemblyFileVersion) + abstractionsPackageVersion: $(abstractionsPackageVersion) mdsAssemblyFileVersion: $(mdsAssemblyFileVersion) mdsPackageVersion: $(mdsPackageVersion) - referenceType: Package - template: /eng/pipelines/common/templates/steps/esrp-code-signing-step.yml@self parameters: artifactType: dll - + - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self parameters: buildConfiguration: Release @@ -72,21 +74,15 @@ jobs: outputDirectory: $(artifactDirectory) packageVersion: $(mdsPackageVersion) referenceType: Package - + - template: /eng/pipelines/common/templates/steps/esrp-code-signing-step.yml@self parameters: artifactType: pkg - + - template: /eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml@self - parameters: - buildConfiguration: Release - referenceType: Package - product: MDS # Publish symbols to servers - - template: /eng/pipelines/common/templates/steps/publish-symbols-step.yml@self - parameters: - buildConfiguration: Release - referenceType: Package - publishSymbols: ${{ parameters['PublishSymbols'] }} - symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(mdsPackageVersion)_$(System.TimelineId) + - ${{ if eq(parameters.publishSymbols, true) }}: + - template: /eng/pipelines/common/templates/steps/publish-symbols-step.yml@self + parameters: + symbolsArtifactName: mds_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(mdsPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index 689b99a09b..c9f34fb435 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -23,14 +23,18 @@ parameters: type: string default: ADO-MMS22-SQL19 + - name: abstractionsArtifactsName + type: string + default: Abstractions.Artifacts + - name: mdsArtifactsName type: string default: MDS.Artifacts - + - name: platform type: string default: $(Platform) - + - name: buildConfiguration type: string values: @@ -41,6 +45,9 @@ parameters: type: stepList default: [] + - name: abstractionsPackageVersion + type: string + - name: mdsPackageVersion type: string @@ -68,15 +75,25 @@ jobs: # configuration - ${{ parameters.prebuildSteps }} - # If we're testing in Package mode, create the packages/ directory and then - # setup the top-level NuGet.config to look in packages/ for local NuGet - # dependencies. + # If we're testing in Package mode, we have a few additional steps. - ${{ if eq(parameters.referenceType, 'Package') }}: + + # Create the packages/ directory. - pwsh: New-Item -Path "$(Build.SourcesDirectory)/packages" -ItemType Directory -Force displayName: Create packages/ directory + + # Setup the top-level NuGet.config to look in packages/ for local NuGet + # dependencies. - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force displayName: Use local NuGet packages + # Download the Abstractions package artifacts. + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifacts + inputs: + artifactName: ${{ parameters.abstractionsArtifactsName }} + targetPath: $(packagePath) + # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self @@ -99,6 +116,7 @@ jobs: referenceType: ${{ parameters.referenceType }} operatingSystem: Windows build: MDS + abstractionsPackageVersion: ${{parameters.abstractionsPackageVersion}} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} - template: /eng/pipelines/common/templates/steps/generate-nuget-package-step.yml@self @@ -106,10 +124,11 @@ jobs: buildConfiguration: ${{ parameters.buildConfiguration }} displayName: 'Create MDS NuGet Package' generateSymbolsPackage: false - packageVersion: ${{ parameters.mdsPackageVersion }} nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' outputDirectory: $(packagePath) + packageVersion: ${{ parameters.mdsPackageVersion }} referenceType: ${{ parameters.referenceType }} + properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' - template: /eng/pipelines/common/templates/steps/ci-project-build-step.yml@self parameters: @@ -129,8 +148,8 @@ jobs: nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' outputDirectory: $(packagePath) packageVersion: $(akvPackageVersion) - properties: 'MdsPackageVersion=${{ parameters.mdsPackageVersion }}' referenceType: ${{ parameters.referenceType }} + properties: 'MdsPackageVersion=${{ parameters.mdsPackageVersion }}' - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifacts' diff --git a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml index 02cc6d58de..51205bf345 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -4,6 +4,12 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactsName + type: string + + - name: abstractionsPackageVersion + type: string + - name: configProperties type: object default: {} # - key: 'value' @@ -110,9 +116,15 @@ jobs: steps: - # If we're testing in Package mode, download the MDS package + # If we're testing in Package mode, download the Abstractions and MDS package # artifacts and put them in the packages/ directory in the repo root. - ${{ if eq(parameters.referenceType, 'Package') }}: + - task: DownloadPipelineArtifact@2 + displayName: Download Abstractions Package Artifacts + inputs: + artifactName: ${{ parameters.abstractionsArtifactsName }} + targetPath: $(Build.SourcesDirectory)/packages + - task: DownloadPipelineArtifact@2 displayName: Download MDS Package Artifacts inputs: @@ -271,6 +283,7 @@ jobs: buildConfiguration: ${{ parameters.buildConfiguration }} referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} ${{ if ne(parameters.operatingSystem, 'Windows') }}: OSGroup: Unix diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml index 70517fbd09..d7a191c20f 100644 --- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -35,20 +35,20 @@ jobs: isCustom: true name: ADO-1ES-Pool vmImage: 'ADO-MMS22-SQL19' - + variables: # More settings at https://aka.ms/obpipelines/yaml/jobs - template: /eng/pipelines/libraries/mds-validation-variables.yml@self steps: - template: /eng/pipelines/common/templates/steps/pre-build-step.yml - + - ${{parameters.downloadPackageStep }} - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force displayName: Use local NuGet packages - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml - parameters: + parameters: TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) SupportsIntegratedSecurity: false @@ -58,14 +58,12 @@ jobs: # build & test - template: /eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml parameters: - referenceType: Package - buildConfiguration: Release ${{ if parameters.isPreview }}: + abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) mdsPackageVersion: $(previewMdsPackageVersion) - template: /eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml parameters: - referenceType: Package - buildConfiguration: Release ${{ if parameters.isPreview }}: + abstractionsPackageVersion: $(abstractionsPackagePreviewVersion) mdsPackageVersion: $(previewMdsPackageVersion) diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index 54c7b2c61f..50e9d693e1 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -4,6 +4,18 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + - name: abstractionsArtifactsName + type: string + + - name: abstractionsPackageVersion + type: string + + - name: azureArtifactsName + type: string + + - name: azurePackageVersion + type: string + - name: buildConfiguration type: string values: @@ -24,7 +36,7 @@ parameters: - name: mdsPackageVersion type: string - + - name: postTestJobs type: jobList default: [] @@ -67,6 +79,10 @@ stages: image: ${{ image.value }} jobDisplayName: ${{ format('{0}_{1}_{2}', replace(targetFramework, '.', '_'), platform, testSet) }} configProperties: ${{ config.value.configProperties }} + abstractionsArtifactsName: ${{ parameters.abstractionsArtifactsName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azureArtifactsName: ${{ parameters.azureArtifactsName }} + azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} @@ -101,9 +117,13 @@ stages: jobDisplayName: ${{ format('{0}_{1}_{2}_{3}', replace(targetFramework, '.', '_'), platform, 'NativeSNI', testSet) }} configProperties: ${{ config.value.configProperties }} useManagedSNI: ${{ useManagedSNI }} + abstractionsArtifactsName: ${{ parameters.abstractionsArtifactsName }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + azureArtifactsName: ${{ parameters.azureArtifactsName }} + azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} - prebuildSteps: ${{ parameters.prebuildSteps }} + prebuildSteps: ${{ parameters.prebuildSteps }} targetFramework: ${{ targetFramework }} netcoreVersionTestUtils: ${{config.value.netcoreVersionTestUtils }} testSet: ${{ testSet }} @@ -122,7 +142,7 @@ stages: - ${{ if ne(length(parameters.postTestJobs), 0) }}: - stage: Post_Test displayName: 'Post Test Jobs' - dependsOn: + dependsOn: - ${{ each config in parameters.testConfigurations }}: - ${{ each image in config.value.images }}: - ${{ image.key }} diff --git a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml index c446ab7baf..02d17dfcec 100644 --- a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml @@ -5,25 +5,20 @@ ################################################################################# parameters: - - name: buildConfiguration + - name: abstractionsAssemblyFileVersion type: string - values: - - Debug - - Release - - name: mdsAssemblyFileVersion + - name: abstractionsPackageVersion type: string - - name: mdsPackageVersion + - name: mdsAssemblyFileVersion type: string - - name: referenceType + - name: mdsPackageVersion type: string - values: - - Package - - Project steps: + # Download our signing key. - task: DownloadSecureFile@1 displayName: 'Download Key Pair' inputs: @@ -33,15 +28,26 @@ steps: # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self + # Configure NuGet to use the local packages/ directory where the Abstractions + # package will be. + - pwsh: New-Item -Path "$(packageFolderName)" -ItemType Directory -Force + displayName: Create packages/ directory + + - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force + displayName: Use local NuGet packages + - task: MSBuild@1 displayName: 'BuildAllConfigurations using build.proj' inputs: solution: '**/build.proj' - configuration: '${{ parameters.buildConfiguration }}' + configuration: Release msbuildArguments: >- -t:BuildAllConfigurations - -p:ReferenceType=${{ parameters.referenceType }} + -p:ReferenceType=Package -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk -p:MdsAssemblyFileVersion=${{ parameters.mdsAssemblyFileVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} + -p:AssemblyFileVersion=${{ parameters.mdsAssemblyFileVersion }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} + -p:AbstractionsAssemblyFileVersion=${{ parameters.abstractionsAssemblyFileVersion }} diff --git a/eng/pipelines/common/templates/steps/build-all-tests-step.yml b/eng/pipelines/common/templates/steps/build-all-tests-step.yml index c8a090c3ff..ef0b997d81 100644 --- a/eng/pipelines/common/templates/steps/build-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-tests-step.yml @@ -4,7 +4,10 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - + + - name: abstractionsPackageVersion + type: string + - name: buildConfiguration type: string values: @@ -30,7 +33,7 @@ parameters: - name: targetFramework type: string - + - name: testSet type: string @@ -47,6 +50,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - ${{elseif eq(parameters.osGroup, '')}}: # .NET on Windows - task: MSBuild@1 @@ -60,6 +64,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) @@ -75,6 +80,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:OSGroup=${{ parameters.osGroup }} -p:platform=${{ parameters.platform }} diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml index 37a0cf87f6..f25b719bc2 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netcore-step.yml @@ -4,21 +4,9 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: TargetNetCoreVersion + - name: abstractionsPackageVersion type: string - default: $(TargetNetCoreVersion) - - - name: buildConfiguration - type: string - values: - - Debug - - Release - - - name: referenceType - default: Project - values: - - Project - - Package + default: $(abstractionsPackageVersion) - name: mdsPackageVersion type: string @@ -28,6 +16,14 @@ parameters: type: string default: $(Platform) + - name: retryCountOnManualTests + type: number + default: 2 + + - name: TargetNetCoreVersion + type: string + default: $(TargetNetCoreVersion) + - name: TestTargetOS type: string default: Windowsnetcoreapp @@ -36,10 +32,6 @@ parameters: - Windowsnetcoreapp - Unixnetcoreapp - - name: retryCountOnManualTests - type: number - default: 2 - steps: - task: MSBuild@1 displayName: 'Build AKV Provider .NET' @@ -47,9 +39,10 @@ steps: solution: build.proj msbuildArchitecture: x64 msbuildArguments: >- - -p:Configuration=${{ parameters.buildConfiguration }} + -p:Configuration=Release -t:BuildAKVNetCore - -p:ReferenceType=${{ parameters.referenceType }} + -p:ReferenceType=Package + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - task: MSBuild@1 @@ -59,10 +52,11 @@ steps: msbuildArchitecture: x64 msbuildArguments: >- -t:BuildTestsNetCore - -p:ReferenceType=${{ parameters.referenceType }} + -p:ReferenceType=Package -p:TargetNetCoreVersion=${{ parameters.TargetNetCoreVersion }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:Configuration=Release # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -76,8 +70,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetCoreVersion=${{ parameters.TargetNetCoreVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -96,8 +91,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetCoreVersion=${{ parameters.TargetNetCoreVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -117,8 +113,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetCoreVersion=${{ parameters.TargetNetCoreVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -139,8 +136,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetCoreVersion=${{ parameters.TargetNetCoreVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n diff --git a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml index ba1d247fee..7db39da4a6 100644 --- a/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml +++ b/eng/pipelines/common/templates/steps/build-and-run-tests-netfx-step.yml @@ -4,21 +4,9 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: TargetNetFxVersion + - name: abstractionsPackageVersion type: string - default: $(TargetNetFxVersion) - - - name: buildConfiguration - type: string - values: - - Debug - - Release - - - name: referenceType - default: Project - values: - - Project - - Package + default: $(abstractionsPackageVersion) - name: mdsPackageVersion type: string @@ -28,6 +16,14 @@ parameters: type: string default: $(Platform) + - name: retryCountOnManualTests + type: number + default: 2 + + - name: TargetNetFxVersion + type: string + default: $(TargetNetFxVersion) + - name: TestTargetOS type: string default: Windowsnetfx @@ -36,10 +32,6 @@ parameters: - Windowsnetcoreapp - Unixnetcoreapp - - name: retryCountOnManualTests - type: number - default: 2 - steps: - task: MSBuild@1 displayName: 'Build AKV Provider .NET Framework' @@ -47,9 +39,10 @@ steps: solution: build.proj msbuildArchitecture: x64 msbuildArguments: >- - -p:Configuration=${{ parameters.buildConfiguration }} + -p:Configuration=Release -t:BuildAKVNetFx - -p:ReferenceType=${{ parameters.referenceType }} + -p:ReferenceType=Package + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - task: MSBuild@1 @@ -58,10 +51,11 @@ steps: solution: build.proj msbuildArguments: >- -t:BuildTestsNetFx - -p:ReferenceType=${{ parameters.referenceType }} + -p:ReferenceType=Package + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:TargetNetFxVersion=${{ parameters.TargetNetFxVersion }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:Configuration=Release -p:Platform=${{ parameters.platform }} # Don't run unit tests using package reference. Unit tests are only run using project reference. @@ -76,8 +70,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetFxVersion=${{ parameters.TargetNetFxVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -97,8 +92,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetFxVersion=${{ parameters.TargetNetFxVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -119,8 +115,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetFxVersion=${{ parameters.TargetNetFxVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n @@ -141,8 +138,9 @@ steps: -p:Platform=${{ parameters.platform }} -p:TestTargetOS=${{ parameters.TestTargetOS }} -p:TargetNetFxVersion=${{ parameters.TargetNetFxVersion }} - -p:ReferenceType=${{ parameters.referenceType }} - -p:Configuration=${{ parameters.buildConfiguration }} + -p:ReferenceType=Package + -p:Configuration=Release + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} --no-build -v n diff --git a/eng/pipelines/common/templates/steps/ci-project-build-step.yml b/eng/pipelines/common/templates/steps/ci-project-build-step.yml index b8e4de6258..bb649d59f3 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -7,13 +7,13 @@ parameters: - name: platform type: string default: $(Platform) - + - name: buildConfiguration type: string values: - Debug - Release - + - name: referenceType type: string values: @@ -23,7 +23,7 @@ parameters: - name: buildNumber type: string default: $(BuildNumber) - + - name: operatingSystem type: string default: deferedToRuntime @@ -32,7 +32,7 @@ parameters: - Linux - MacOS - deferedToRuntime - + - name: build type: string default: MDS @@ -41,8 +41,13 @@ parameters: - AKV - all - allNoDocs - - # Necessary when referenceType is Package. + + # Necessary to build MDS when referenceType is Package + - name: abstractionsPackageVersion + type: string + default: '' + + # Necessary to build AKV when referenceType is Package. - name: mdsPackageVersion type: string default: '' @@ -59,6 +64,7 @@ steps: msbuildArguments: >- -t:restore -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} retryCountOnTaskFailure: 1 @@ -77,6 +83,7 @@ steps: -p:GenerateNuget=false -p:GenerateDocumentationFile=false -p:BuildNumber=${{ parameters.buildNumber }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all')) }}: @@ -88,11 +95,12 @@ steps: msbuildArchitecture: x64 platform: '${{ parameters.platform }}' configuration: '${{ parameters.buildConfiguration }}' - msbuildArguments: + msbuildArguments: -t:BuildAllConfigurations -p:ReferenceType=${{ parameters.referenceType }} -p:GenerateNuget=false -p:BuildNumber=${{ parameters.buildNumber }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} - ${{ if or(eq(parameters.build, 'AKV'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: @@ -141,6 +149,7 @@ steps: -p:GenerateNuget=false -p:GenerateDocumentationFile=false -p:Configuration=${{ parameters.buildConfiguration }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} verbosityRestore: Detailed verbosityPack: Detailed diff --git a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml index 353583e7b9..3c00b3883c 100644 --- a/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml +++ b/eng/pipelines/common/templates/steps/copy-dlls-for-test-step.yml @@ -4,24 +4,13 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: - - name: buildConfiguration - type: string - values: - - Debug - - Release - - name: symbolsFolder type: string default: symbols - + - name: softwareFolder type: string default: software - - - name: referenceType - values: - - Project - - Package - name: listOfTF type: object @@ -30,12 +19,6 @@ parameters: - net8.0 - net9.0 - - name: product - default: MDS - values: - - MDS - - MSS - steps: - powershell: | $software = '${{parameters.softwareFolder}}' @@ -47,38 +30,37 @@ steps: md $symbols md $symbols\win displayName: 'Make base directories' - + - ${{ each targetFramework in parameters.listOfTF }}: - - ${{ if eq(parameters.product, 'MDS') }}: - - powershell: | - $software = '${{parameters.softwareFolder}}' - $tf = '${{ targetFramework }}' - md $software\win\$tf - - if ($tf.StartsWith('net4')) - { - Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.buildConfiguration }}.AnyCPU\Microsoft.Data.SqlClient\netfx\$tf\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse - } - else - { - Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.buildConfiguration }}.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse - } - - $symbols = '${{parameters.symbolsFolder}}' - md $symbols\win\$tf - - if ($tf.StartsWith('net4')) - { - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\$tf\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse - } - else - { - Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse - } - - Write-Host "Artifacts fetched for testing" - Get-Location - displayName: 'Prepare ${{ targetFramework }} Artifacts for Testing' + - powershell: | + $software = '${{parameters.softwareFolder}}' + $tf = '${{ targetFramework }}' + md $software\win\$tf + + if ($tf.StartsWith('net4')) + { + Copy-Item "artifacts\Package\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\$tf\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse + } + else + { + Copy-Item "artifacts\Package\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.dll" "$software\win\$tf" -recurse + } + + $symbols = '${{parameters.symbolsFolder}}' + md $symbols\win\$tf + + if ($tf.StartsWith('net4')) + { + Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netfx\$tf\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse + } + else + { + Copy-Item "artifacts\Project\bin\Windows_NT\Release.AnyCPU\Microsoft.Data.SqlClient\netcore\$tf\Microsoft.Data.SqlClient.pdb" "$symbols\win\$tf" -recurse + } + + Write-Host "Artifacts fetched for testing" + Get-Location + displayName: 'Prepare ${{ targetFramework }} Artifacts for Testing' - powershell: | $software = '${{parameters.softwareFolder}}' diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index 2c394055a0..894730ab03 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -10,9 +10,6 @@ parameters: type: string default: 'SqlClientDrivers' - - name: publishSymbols - type: string - - name: symbolsVersion type: string default: '$(mdsPackageVersion)' @@ -20,63 +17,42 @@ parameters: - name: symbolServer type: string default: '$(SymbolServer)' - + - name: symbolTokenUri type: string default: '$(SymbolTokenUri)' - + - name: symbolsArtifactName type: string - + - name: publishToServers type: object - default: + default: internal: true public: true - - name: referenceType - values: - - Project - - Package - - - name: product - default: MDS - values: - - MDS - - MSS - - - name: buildConfiguration - type: string - values: - - Debug - - Release - steps: - powershell: 'Write-Host "##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{parameters.SymAccount}}"' displayName: 'Update Symbol.AccountName with ${{parameters.SymAccount}}' - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) - -- ${{ if eq(parameters.product, 'MDS') }}: - - task: PublishSymbols@2 - displayName: 'Upload symbols to ${{parameters.SymAccount }} org' - inputs: - SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' - SearchPattern: | - Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb - Unix/${{ parameters.buildConfiguration }}.AnyCPU/**/Microsoft.Data.SqlClient.pdb - IndexSources: false - SymbolServerType: TeamServices - SymbolsMaximumWaitTime: 60 - SymbolExpirationInDays: 1825 # 5 years - SymbolsProduct: Microsoft.Data.SqlClient - SymbolsVersion: ${{parameters.symbolsVersion }} - SymbolsArtifactName: ${{parameters.symbolsArtifactName }} - Pat: $(System.AccessToken) - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) + +- task: PublishSymbols@2 + displayName: 'Upload symbols to ${{parameters.SymAccount }} org' + inputs: + SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\Package\bin' + SearchPattern: | + Windows_NT/Release.AnyCPU/**/Microsoft.Data.SqlClient.pdb + Unix/Release.AnyCPU/**/Microsoft.Data.SqlClient.pdb + IndexSources: false + SymbolServerType: TeamServices + SymbolsMaximumWaitTime: 60 + SymbolExpirationInDays: 1825 # 5 years + SymbolsProduct: Microsoft.Data.SqlClient + SymbolsVersion: ${{parameters.symbolsVersion }} + SymbolsArtifactName: ${{parameters.symbolsArtifactName }} + Pat: $(System.AccessToken) - task: AzureCLI@2 displayName: 'Publish symbols' - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) inputs: azureSubscription: 'Symbols publishing Workload Identity federation service-ADO.Net' scriptType: ps @@ -87,7 +63,7 @@ steps: echo "Publishing request name: ${{parameters.symbolsArtifactName }}" echo "Publish to internal server: $publishToInternalServer" - echo "Publish to public server: $publishToPublicServer" + echo "Publish to public server: $publishToPublicServer" $symbolServer = "${{parameters.symbolServer }}" $tokenUri = "${{parameters.symbolTokenUri }}" @@ -117,16 +93,16 @@ steps: echo "> 4.Checking the status of the request ..." Invoke-RestMethod -Method GET -Uri "https://$symbolServer.trafficmanager.net/projects/$projectName/requests/$requestName" -Headers @{ Authorization = "Bearer $symbolPublishingToken" } -ContentType "application/json" - + echo "Use below tables to interpret the values of xxxServerStatus and xxxServerResult fields from the response." - + echo "PublishingStatus" echo "-----------------" echo "0 NotRequested; The request has not been requested to publish." echo "1 Submitted; The request is submitted to be published" echo "2 Processing; The request is still being processed" echo "3 Completed; The request has been completed processing. It can be failed or successful. Check PublishingResult to get more details" - + echo "PublishingResult" echo "-----------------" echo "0 Pending; The request has not completed or has not been requested." diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index d88065f44a..f4b26cab1c 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -4,6 +4,10 @@ # See the LICENSE file in the project root for more information. # ################################################################################# parameters: + + - name: abstractionsPackageVersion + type: string + - name: debug type: boolean default: false @@ -13,12 +17,11 @@ parameters: - name: mdsPackageVersion type: string - default: $(mdsPackageVersion) - name: platform type: string default: $(Platform) - + - name: buildConfiguration type: string values: @@ -39,11 +42,11 @@ parameters: values: - x64 - x86 - + - name: dotnetx86RootPath # full path to the x86 dotnet root folder with trailing slash type: string default: '' - + - name: operatingSystem type: string default: 'Windows' @@ -68,16 +71,14 @@ steps: msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} ${{ else }}: # x86 msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - task: MSBuild@1 @@ -88,18 +89,16 @@ steps: msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} - -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false @@ -113,12 +112,13 @@ steps: msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- -t:RunFunctionalTests -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} ${{ else }}: # x86 msbuildArguments: >- @@ -126,6 +126,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} @@ -137,12 +138,13 @@ steps: msbuildArchitecture: ${{parameters.msbuildArchitecture }} platform: '${{parameters.platform }}' configuration: '${{parameters.buildConfiguration }}' - ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: msbuildArguments: >- -t:RunFunctionalTests -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false @@ -152,6 +154,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:Filter="category=flaky" @@ -172,6 +175,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} ${{ else }}: # x86 msbuildArguments: >- @@ -179,6 +183,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} retryCountOnTaskFailure: ${{parameters.retryCountOnManualTests }} @@ -197,6 +202,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false @@ -206,6 +212,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:Filter="category=flaky" @@ -227,12 +234,13 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} verbosityRestore: Detailed verbosityPack: Detailed - + - task: DotNetCoreCLI@2 displayName: 'Run Flaky Unit Tests' condition: succeededOrFailed() @@ -245,6 +253,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} @@ -265,6 +274,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} @@ -283,6 +293,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} @@ -303,6 +314,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} @@ -322,6 +334,7 @@ steps: -p:TF=${{ parameters.targetFramework }} -p:TestSet=${{ parameters.testSet }} -p:ReferenceType=${{ parameters.referenceType }} + -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} -p:platform=${{ parameters.platform }} -p:Configuration=${{ parameters.buildConfiguration }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index d287e8b254..b08ce8d76b 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -110,7 +110,7 @@ parameters: variables: - template: /eng/pipelines/libraries/ci-build-variables.yml@self - + - name: abstractionsArtifactsName value: Abstractions.Artifacts @@ -149,6 +149,8 @@ stages: parameters: buildConfiguration: ${{ parameters.buildConfiguration }} referenceType: ${{ parameters.referenceType }} + abstractionsPackageVersion: $(abstractionsPackageVersion) + abstractionsArtifactsName: $(abstractionsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) mdsArtifactsName: $(mdsArtifactsName) ${{if ne(parameters.SNIVersion, '')}}: @@ -168,16 +170,15 @@ stages: azurePackageVersion: $(azurePackageVersion) buildConfiguration: ${{ parameters.buildConfiguration }} debug: ${{ parameters.debug }} - ${{if eq(parameters.debug, 'true')}}: - dotnetVerbosity: diagnostic - mdsArtifactsName: $(mdsArtifactsName) - mdsPackageVersion: $(mdsPackageVersion) # When building via packages, we must depend on the Abstractions and MDS # packages. ${{ if eq(parameters.referenceType, 'Package') }}: dependsOn: - build_abstractions_package_stage - build_mds_akv_packages_stage + dotnetVerbosity: diagnostic + mdsArtifactsName: $(mdsArtifactsName) + mdsPackageVersion: $(mdsPackageVersion) referenceType: ${{ parameters.referenceType }} # Run the stress tests, if desired. @@ -187,16 +188,22 @@ stages: buildConfiguration: ${{ parameters.buildConfiguration }} dependsOn: - build_mds_akv_packages_stage + - build_azure_package_stage mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) + azurePackageVersion: $(azurePackageVersion) dotnetVerbosity: ${{ parameters.dotnetVerbosity }} - + # Run the MDS and AKV tests. - template: /eng/pipelines/common/templates/stages/ci-run-tests-stage.yml@self parameters: debug: ${{ parameters.debug }} buildConfiguration: ${{ parameters.buildConfiguration }} referenceType: ${{ parameters.referenceType }} + abstractionsArtifactsName: $(abstractionsArtifactsName) + abstractionsPackageVersion: $(abstractionsPackageVersion) + azureArtifactsName: $(azureArtifactsName) + azurePackageVersion: $(azurePackageVersion) mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} @@ -222,6 +229,8 @@ stages: displayName: '[Debug] List Environment Variables' - ${{if eq(parameters.referenceType, 'Package')}}: + - pwsh: New-Item -Path "$(Build.SourcesDirectory)/packages" -ItemType Directory -Force + displayName: Create packages/ directory - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force displayName: Use local NuGet packages @@ -341,10 +350,10 @@ stages: # skipSqlConfiguration: # skips the SQL configuration step - windows_sql_22_x64: + windows_sql_22_x64: pool: ${{parameters.defaultPoolName }} images: - Win22_Sql22: ADO-MMS22-SQL22 + Win22_Sql22: ADO-MMS22-SQL22 TargetFrameworks: ${{parameters.targetFrameworks }} netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} buildPlatforms: ${{parameters.buildPlatforms }} @@ -370,10 +379,10 @@ stages: enableLocalDB: true - windows_sql_22_x86: + windows_sql_22_x86: pool: ${{parameters.defaultPoolName }} images: - Win22_Sql22_x86: ADO-MMS22-SQL22 + Win22_Sql22_x86: ADO-MMS22-SQL22 TargetFrameworks: [net462, net8.0, net9.0] netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} buildPlatforms: ${{parameters.buildPlatforms }} @@ -424,8 +433,8 @@ stages: # Azure SQL Server - Windows windows_azure_sql: - pool: ${{parameters.defaultPoolName }} - images: + pool: ${{parameters.defaultPoolName }} + images: Win22_Azure_Sql: ADO-MMS22-SQL19 Win11_Azure_Sql: ADO-CI-Win11 TargetFrameworks: ${{parameters.targetFrameworks }} @@ -457,7 +466,7 @@ stages: windows_azure_arm64_sql: pool: ADO-CI-PUBLIC-ARM64-1ES-EUS-POOL - images: + images: Win11_ARM64_Azure_Sql: ADO-WIN11-ARM64 isArm64: true TargetFrameworks: ${{parameters.targetFrameworks }} diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml index 092d34ec4c..22905f23de 100644 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -21,6 +21,12 @@ parameters: + # The Azure package version to stress test. This version must be available in + # one of the configured NuGet sources. + - name: azurePackageVersion + displayName: Azure Package Version + type: string + # The type of build to produce (Debug or Release) - name: buildConfiguration type: string @@ -51,11 +57,9 @@ parameters: type: string default: MDS.Artifacts - # The MDS package version to stress test. This version must be available in - # one of the configured NuGet sources. + # The MDS package version found in the pipeline artifacts we will download. - name: mdsPackageVersion type: string - default: '' # The list of .NET Framework runtimes to test against. - name: netFrameworkTestRuntimes @@ -92,6 +96,7 @@ stages: --verbosity ${{parameters.dotnetVerbosity}} --artifacts-path $(dotnetArtifactsDir) -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} + -p:AzurePackageVersion=${{parameters.azurePackageVersion}} # dotnet CLI arguments for build/run commands. - name: buildArguments @@ -130,7 +135,7 @@ stages: jobNameSuffix: linux displayNamePrefix: Linux poolName: $(ci_var_defaultPoolName) - vmImage: ADO-UB20-SQL22 + vmImage: ADO-UB22-SQL22 sqlSetupStep: /eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml mdsArtifactsName: ${{ parameters.mdsArtifactsName }} solution: $(solution) From af49e59d09fa849c1ec81a46856407e17535b401 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:15:51 -0400 Subject: [PATCH 03/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Removed Azure parameters from CI test tree. --- .../common/templates/stages/ci-run-tests-stage.yml | 10 ---------- eng/pipelines/dotnet-sqlclient-ci-core.yml | 2 -- 2 files changed, 12 deletions(-) diff --git a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml index 50e9d693e1..defba05d9f 100644 --- a/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml +++ b/eng/pipelines/common/templates/stages/ci-run-tests-stage.yml @@ -10,12 +10,6 @@ parameters: - name: abstractionsPackageVersion type: string - - name: azureArtifactsName - type: string - - - name: azurePackageVersion - type: string - - name: buildConfiguration type: string values: @@ -81,8 +75,6 @@ stages: configProperties: ${{ config.value.configProperties }} abstractionsArtifactsName: ${{ parameters.abstractionsArtifactsName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} - azureArtifactsName: ${{ parameters.azureArtifactsName }} - azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} @@ -119,8 +111,6 @@ stages: useManagedSNI: ${{ useManagedSNI }} abstractionsArtifactsName: ${{ parameters.abstractionsArtifactsName }} abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} - azureArtifactsName: ${{ parameters.azureArtifactsName }} - azurePackageVersion: ${{ parameters.azurePackageVersion }} mdsArtifactsName: ${{ parameters.mdsArtifactsName }} mdsPackageVersion: ${{ parameters.mdsPackageVersion }} prebuildSteps: ${{ parameters.prebuildSteps }} diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index b08ce8d76b..fa87e5b3d2 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -202,8 +202,6 @@ stages: referenceType: ${{ parameters.referenceType }} abstractionsArtifactsName: $(abstractionsArtifactsName) abstractionsPackageVersion: $(abstractionsPackageVersion) - azureArtifactsName: $(azureArtifactsName) - azurePackageVersion: $(azurePackageVersion) mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) testJobTimeout: ${{ parameters.testJobTimeout }} From dbb1e157509af6c1828d70244d589a2822bbcb8d Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 22 Jan 2026 12:26:25 -0400 Subject: [PATCH 04/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Fixed missing Abstractions and MDS package version parameters. --- eng/pipelines/common/templates/jobs/ci-run-tests-job.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml index 51205bf345..316545bedb 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -297,6 +297,8 @@ jobs: referenceType: ${{ parameters.referenceType }} testSet: ${{ parameters.testSet }} operatingSystem: ${{ parameters.operatingSystem }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: # Set up for x86 tests by manually installing dotnet for x86 to an alternative location. This @@ -323,6 +325,8 @@ jobs: msbuildArchitecture: x86 dotnetx86RootPath: $(dotnetx86RootPath) operatingSystem: ${{ parameters.operatingSystem }} + abstractionsPackageVersion: ${{ parameters.abstractionsPackageVersion }} + mdsPackageVersion: ${{ parameters.mdsPackageVersion }} - ${{ if eq(parameters.publishTestResults, true) }}: - template: /eng/pipelines/common/templates/steps/publish-test-results-step.yml@self From 313e26a6ff24d79a3240cd765753c8fd98a28063 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:51:40 -0400 Subject: [PATCH 05/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Re-activate the Azure tests. - Address PR comments. --- .../src/SqlAuthenticationProvider.Internal.cs | 3 ++- .../Azure/test/AADAuthenticationTests.cs | 6 ------ .../Azure/test/AADConnectionTest.cs | 6 ------ .../Azure/test/Azure.Test.csproj | 13 ++++--------- .../Azure/test/Config.cs | 6 ++---- .../Azure/test/DefaultAuthProviderTests.cs | 6 ------ .../Connection/SqlConnectionInternal.cs | 12 ++++++------ .../SqlAuthenticationProviderManager.cs | 18 ++++++++---------- 8 files changed, 22 insertions(+), 48 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs index a561779ef4..65dd9bd54e 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs @@ -53,7 +53,8 @@ static Internal() return; } - // TODO(ADO-39845): Verify the assembly is signed by us? + // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39845): + // Verify the assembly is signed by us? // Look for the manager class. const string className = "Microsoft.Data.SqlClient.SqlAuthenticationProviderManager"; diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs index ac8a98cf13..5c7572ec84 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADAuthenticationTests.cs @@ -9,10 +9,6 @@ namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; -// TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39233): -// Enable this file once the MDS Azure files have been removed. -#if false - // These tests were moved from MDS FunctionalTests AADAuthenticationTests.cs. public class AADAuthenticationTests { @@ -40,5 +36,3 @@ public void CustomActiveDirectoryProviderTest_AppClientId_DeviceFlowCallback() Assert.Same(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } } - -#endif diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs index c78068575f..8c4006c5b4 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/AADConnectionTest.cs @@ -7,10 +7,6 @@ // This file has intentionally not been tidied up or modernized. Its content will be absorbed into // new unit and/or integration tests in the future. -// TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39233): -// Enable this file once the MDS Azure files have been removed. -#if false - using System.Text.RegularExpressions; namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; @@ -401,5 +397,3 @@ public static string RetrieveValueFromConnStr(string connStr, string[] keywords) #endregion } - -#endif diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index 3ace8e9379..76e3e1a59a 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -41,19 +41,14 @@ - - - + - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs index ff40a0c7e9..86c876c068 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Config.cs @@ -63,10 +63,8 @@ internal static class Config internal static bool HasUserManagedIdentityClientId() => !UserManagedIdentityClientId.IsEmpty(); internal static bool HasWorkloadIdentityFederationServiceConnectionId() => !WorkloadIdentityFederationServiceConnectionId.IsEmpty(); - // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39233): - // Uncomment this once the MDS Azure files have been removed. - // internal static bool IsAzureSqlServer() => - // Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TcpConnectionString).DataSource); + internal static bool IsAzureSqlServer() => + Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TcpConnectionString).DataSource); internal static bool OnAdoPool() => AdoPool; internal static bool OnLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs index e282c496cc..ca06dacc20 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -// TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39233): -// Enable this file once the MDS Azure files have been removed. -#if false - namespace Microsoft.Data.SqlClient.Extensions.Azure.Test; public class DefaultAuthProviderTests @@ -68,5 +64,3 @@ public void AuthProviderInstalled() } } } - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index efd54ae383..4c8d6c1cac 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -1674,7 +1674,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) $"SqlInternalConnectionTds.OnFeatureExtAck | ERR | " + $"Object ID {ObjectID}, " + $"Unknown token for JSONSUPPORT"); - + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } @@ -2691,7 +2691,7 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) const int defaultRetryPeriod = 100; // Number of attempts we are willing to perform. - const int maxAttempts = 1; + const int maxAttempts = 2; // Username to use in error messages. string? username = null; @@ -2704,7 +2704,7 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // We will perform retries if the provider indicates an error that // is retryable. - for (int attempt = 0; attempt <= maxAttempts; ++attempt) + for (int attempt = 1; attempt <= maxAttempts; ++attempt) { try { @@ -2809,7 +2809,7 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) authParamsBuilder.WithPassword(ConnectionOptions.Password); } SqlAuthenticationParameters parameters = authParamsBuilder; - CancellationTokenSource cts = new(); + using CancellationTokenSource cts = new(); // Use Connection timeout value to cancel token acquire request after certain period of time.(int) if (_timeout.MillisecondsRemaining < Int32.MaxValue) { @@ -2845,12 +2845,12 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), ex.FailureCode, ex); } - // We use a doubling backoff if the provider didn't provide + // We use a linear backoff if the provider didn't provide // a retry period. int retryPeriod = ex.RetryPeriod > 0 ? ex.RetryPeriod - : defaultRetryPeriod * (2 ^ attempt); + : defaultRetryPeriod * attempt; SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, sleeping {2}[Milliseconds]", ObjectID, attempt, retryPeriod); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Attempt: {1}, remaining {2}[Milliseconds]", ObjectID, attempt, _timeout.MillisecondsRemaining); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 06d7423028..f574efd2cb 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -66,7 +66,8 @@ static SqlAuthenticationProviderManager() return; } - // TODO(ADO-39845): Verify the assembly is signed by us? + // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39845): + // Verify the assembly is signed by us? SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + @@ -262,18 +263,15 @@ internal static bool SetProvider(SqlAuthenticationMethod authenticationMethod, S throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); } var methodName = "SetProvider"; - if (Instance._authenticationsWithAppSpecifiedProvider.Count > 0) + foreach (SqlAuthenticationMethod candidateMethod in Instance._authenticationsWithAppSpecifiedProvider) { - foreach (SqlAuthenticationMethod candidateMethod in Instance._authenticationsWithAppSpecifiedProvider) + if (candidateMethod == authenticationMethod) { - if (candidateMethod == authenticationMethod) - { - Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); + Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); - // The app has already specified a Provider for this - // authentication method, so we won't override it. - return false; - } + // The app has already specified a Provider for this + // authentication method, so we won't override it. + return false; } } Instance._providers.AddOrUpdate( From ad7ea2f6478de901a08b46b35af5377f5f39309b Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:33:17 -0400 Subject: [PATCH 06/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Addressed a few final comments from the Step 2 PR. --- .../Abstractions/README.md | 6 +++--- .../Azure/test/WorkloadIdentityFederationTests.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md index fbb8bfb738..608f49034c 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/README.md @@ -149,7 +149,7 @@ feature implementations via the appropriate APIs at runtime: - Authentication: [SqlAuthenticationProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlauthenticationprovider?view=sqlclient-dotnet-core-6.0) - Attestation: _**New API will be exposed.**_ -- Key Valut: [SqlColumnEncryptionKeyStoreProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcolumnencryptionkeystoreprovider?view=sqlclient-dotnet-core-6.0) +- Key Vault: [SqlColumnEncryptionKeyStoreProvider](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcolumnencryptionkeystoreprovider?view=sqlclient-dotnet-core-6.0) ## Versioning Strategy @@ -181,7 +181,7 @@ packages as well. Those dependent packages will then take a strict dependency on the appropriate `Abstractions` package version. This ensures that only compatible extensions package versions can co-exist with the main MDS package. -For example, imagine that a new extensible conenction pooling feature is added +For example, imagine that a new extensible connection pooling feature is added to MDS. The `Abstractions` package would be updated to include any new pooling APIs, the main MDS package would be updated to accept extensible pooling, and the new pooling implementation would be included in a new `ConnectionPooling` @@ -269,7 +269,7 @@ package will look for the `Azure` extension assembly and automatically load it. #### Use AKV Provider v7.0.0 This is a temporary solution. The AKV provider v7.0.0 will be marked as -deprecated and removed entirely at some point in the future. The applictaion +deprecated and removed entirely at some point in the future. The application would remain dependent on the AKV provider, but must update to the v7.0.0 package. Previous AKV package versions do not support main MDS package versions beyond the v6.x range. diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs index 053e543ee1..4f079b92c2 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/WorkloadIdentityFederationTests.cs @@ -16,7 +16,7 @@ public class WorkloadIdentityFederationTests nameof(Config.HasTenantId), nameof(Config.HasUserManagedIdentityClientId), nameof(Config.HasWorkloadIdentityFederationServiceConnectionId))] - public async void GetCredential() + public async Task GetCredential() { AzurePipelinesCredential credential = new( // The tenant ID of the managed identity associated to our workload @@ -32,7 +32,7 @@ public async void GetCredential() // The client ID of the managed identity associated to our workload // identity federation service connection. See: // - // https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/654fffd0-d02d-4894-b1b7-e2dfbc44a665/resourceGroups/aad-testlab-dl797892652000/providers/Microsoft.ManagedIdentity/userAssignedIdentities/dotnetMSI/overview + // https://ms.portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/654fffd0-d02d-4894-b1b7-e2dfbc44a665/resourceGroups/aad-testlab-dl797892652000/providers/Microsoft.ManagedIdentity/userAssignedIdentities/dotnetMSI/overview // Config.UserManagedIdentityClientId, @@ -60,6 +60,6 @@ public async void GetCredential() new(["https://database.windows.net/.default"]), CancellationToken.None); - Assert.NotEmpty(token.Token); + Assert.NotEmpty(token.Token); } } From 19c0963b9fcb2361c3a6ada0a09ff3f86f429b68 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:42:30 -0400 Subject: [PATCH 07/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Fixed missing ; --- .../Azure/test/DefaultAuthProviderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs index ca06dacc20..84d32651d5 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/DefaultAuthProviderTests.cs @@ -26,9 +26,9 @@ public void AuthProviderInstalled() // via Theory data so that we detect any new methods that don't meet // our expectations. #if NET - var methods = Enum.GetValues() + var methods = Enum.GetValues(); #else - var methods = Enum.GetValues(typeof(SqlAuthenticationMethod)).Cast() + var methods = Enum.GetValues(typeof(SqlAuthenticationMethod)).Cast(); #endif foreach (var method in methods) From 86f06f6b367a9050cae7cc3964b8d45e8b3ce043 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:50:02 -0400 Subject: [PATCH 08/15] - Fixed missing SNI DLLs for .NET Framework in Project reference tests. - Trying to fix test filters. --- eng/pipelines/dotnet-sqlclient-ci-core.yml | 2 +- eng/pipelines/jobs/test-abstractions-package-ci-job.yml | 8 ++++---- eng/pipelines/jobs/test-azure-package-ci-job.yml | 8 ++++---- .../Azure/test/Azure.Test.csproj | 9 +++++++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index fa87e5b3d2..d765707508 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -176,7 +176,7 @@ stages: dependsOn: - build_abstractions_package_stage - build_mds_akv_packages_stage - dotnetVerbosity: diagnostic + dotnetVerbosity: ${{ parameters.dotnetVerbosity }} mdsArtifactsName: $(mdsArtifactsName) mdsPackageVersion: $(mdsPackageVersion) referenceType: ${{ parameters.referenceType }} diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index 34838ddb0c..e2ef220c1f 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -170,7 +170,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category != failing & category != flaky"' + --filter 'category != failing & category != flaky' - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -182,7 +182,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category = flaky"' + --filter 'category = flaky' # Run the tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkRuntimes }}: @@ -196,7 +196,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category != failing & category != flaky"' + --filter 'category != failing & category != flaky' - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -208,4 +208,4 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category = flaky"' + --filter 'category = flaky' diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index d6131d876d..1dd802d16a 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -305,7 +305,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category != failing & category != flaky"' + --filter 'category != failing & category != flaky' - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -324,7 +324,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category = flaky"' + --filter 'category = flaky' # Run the tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkRuntimes }}: @@ -345,7 +345,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category != failing & category != flaky"' + --filter 'category != failing & category != flaky' - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -364,4 +364,4 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter '"category = flaky"' + --filter 'category = flaky' diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj index 76e3e1a59a..e116125733 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj @@ -44,6 +44,15 @@ + + + From eac8d451538d4787ef8a1270ab7837eb01920715 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 3 Feb 2026 12:02:42 -0400 Subject: [PATCH 09/15] Trying to fix test filters with quoted filter expression, since it contains spaces and special characters. --- eng/pipelines/jobs/test-abstractions-package-ci-job.yml | 8 ++++---- eng/pipelines/jobs/test-azure-package-ci-job.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml index e2ef220c1f..a5b9aabf32 100644 --- a/eng/pipelines/jobs/test-abstractions-package-ci-job.yml +++ b/eng/pipelines/jobs/test-abstractions-package-ci-job.yml @@ -170,7 +170,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category != failing & category != flaky' + --filter "category != failing & category != flaky" - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -182,7 +182,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category = flaky' + --filter "category = flaky" # Run the tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkRuntimes }}: @@ -196,7 +196,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category != failing & category != flaky' + --filter "category != failing & category != flaky" - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -208,4 +208,4 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category = flaky' + --filter "category = flaky" diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 1dd802d16a..a67f4f1652 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -305,7 +305,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category != failing & category != flaky' + --filter "category != failing & category != flaky" - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -324,7 +324,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category = flaky' + --filter "category = flaky" # Run the tests for each .NET Framework runtime. - ${{ each runtime in parameters.netFrameworkRuntimes }}: @@ -345,7 +345,7 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category != failing & category != flaky' + --filter "category != failing & category != flaky" - task: DotNetCoreCLI@2 displayName: Test Flaky [${{ runtime }}] @@ -364,4 +364,4 @@ jobs: $(buildArguments) --no-build -f ${{ runtime }} - --filter 'category = flaky' + --filter "category = flaky" From 1544e3c36952b44e46e0c5d0b22a16a2cc6140d9 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:09:24 -0400 Subject: [PATCH 10/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Addressed PR comments/suggestions. --- .gitignore | 8 ++-- BUILDGUIDE.md | 20 ++------- NuGet.config | 17 ++++--- NuGet.config.local | 16 ------- build.proj | 13 ++++-- .../templates/jobs/ci-build-nugets-job.yml | 17 ++----- .../jobs/run-tests-package-reference-job.yml | 3 -- ...ld-all-configurations-signed-dlls-step.yml | 8 ---- .../templates/steps/override-sni-version.yml | 17 ++++--- eng/pipelines/dotnet-sqlclient-ci-core.yml | 6 --- .../jobs/pack-azure-package-ci-job.yml | 10 +---- eng/pipelines/jobs/stress-tests-ci-job.yml | 2 - .../jobs/test-azure-package-ci-job.yml | 4 -- packages/.gitkeep | 2 + .../Connection/SqlConnectionInternal.cs | 4 ++ .../SqlAuthenticationProviderManager.cs | 27 ++++++++---- .../SqlAuthenticationProviderManagerTests.cs | 44 ++++++++++++++++++- .../tests/StressTests/NuGet.config | 13 ------ 18 files changed, 110 insertions(+), 121 deletions(-) delete mode 100644 NuGet.config.local create mode 100644 packages/.gitkeep delete mode 100644 src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config diff --git a/.gitignore b/.gitignore index bb6f58f9bd..39a5180aed 100644 --- a/.gitignore +++ b/.gitignore @@ -193,12 +193,10 @@ PublishScripts/ *.nupkg # NuGet Symbol Packages *.snupkg -# The packages folder can be ignored because of Package Restore +# Most of the packages folder can be ignored. **/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config +!**/[Pp]ackages/.gitkeep + # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 5867ddcbfb..dc58da3e72 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -211,27 +211,13 @@ dependencies. Alternatively, the `ReferenceType` build property may be specified with a value of `Package`. This will change inter-component dependencies to use `` dependencies, and require that dependent components be -built and packaged before building the depending component. In this scenario, -the root `NuGet.config` file must be updated to include the following entry -under the `` element: - -```xml - - - ... - - - -``` - -As a convenience, a `NuGet.config.local` file is supplied with the above -package source already present. You may simply copy it over `NuGet.config` -when using `Package` references. +built and packaged before building the depending component. This will generate NuGet +packages in the root packages/ directory, and will be automatically searched by NuGet +(see our root `NuGet.config`). Then, you can specify `Package` references be used, for example: ```bash -cp NuGet.config.local NuGet.config dotnet build -t:BuildAbstractions dotnet build -t:BuildAzure -p:ReferenceType=Package dotnet build -t:BuildAll -p:ReferenceType=Package diff --git a/NuGet.config b/NuGet.config index 6c4f535818..919c0dc628 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,18 +1,25 @@  - + + + + + diff --git a/NuGet.config.local b/NuGet.config.local deleted file mode 100644 index db2a1da706..0000000000 --- a/NuGet.config.local +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - diff --git a/build.proj b/build.proj index a2026334f0..505b2d0c3b 100644 --- a/build.proj +++ b/build.proj @@ -285,11 +285,14 @@ + $(DotnetPath)dotnet test "@(UnitTestsProj)" -f $(TF) -p:Configuration=$(Configuration) - -p:ReferenceType=Project $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -312,7 +315,6 @@ $(DotnetPath)dotnet test "@(UnitTestsProj)" -f $(TF) -p:Configuration=$(Configuration) - -p:ReferenceType=Project $(CollectStatement) --results-directory $(ResultsDirectory) --filter "$(FilterStatement)" @@ -446,7 +448,12 @@ - + + + + + + diff --git a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml index c9f34fb435..69f87a7fa9 100644 --- a/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-build-nugets-job.yml @@ -75,19 +75,8 @@ jobs: # configuration - ${{ parameters.prebuildSteps }} - # If we're testing in Package mode, we have a few additional steps. + # If we're testing in Package mode, then we must download the Abstractions package artifacts. - ${{ if eq(parameters.referenceType, 'Package') }}: - - # Create the packages/ directory. - - pwsh: New-Item -Path "$(Build.SourcesDirectory)/packages" -ItemType Directory -Force - displayName: Create packages/ directory - - # Setup the top-level NuGet.config to look in packages/ for local NuGet - # dependencies. - - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force - displayName: Use local NuGet packages - - # Download the Abstractions package artifacts. - task: DownloadPipelineArtifact@2 displayName: Download Abstractions Package Artifacts inputs: @@ -127,8 +116,8 @@ jobs: nuspecPath: 'tools/specs/Microsoft.Data.SqlClient.nuspec' outputDirectory: $(packagePath) packageVersion: ${{ parameters.mdsPackageVersion }} - referenceType: ${{ parameters.referenceType }} properties: 'AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }}' + referenceType: ${{ parameters.referenceType }} - template: /eng/pipelines/common/templates/steps/ci-project-build-step.yml@self parameters: @@ -148,8 +137,8 @@ jobs: nuspecPath: 'tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec' outputDirectory: $(packagePath) packageVersion: $(akvPackageVersion) - referenceType: ${{ parameters.referenceType }} properties: 'MdsPackageVersion=${{ parameters.mdsPackageVersion }}' + referenceType: ${{ parameters.referenceType }} - task: PublishPipelineArtifact@1 displayName: 'Publish Pipeline Artifacts' diff --git a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml index d7a191c20f..a6e7755c6b 100644 --- a/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml +++ b/eng/pipelines/common/templates/jobs/run-tests-package-reference-job.yml @@ -44,9 +44,6 @@ jobs: - ${{parameters.downloadPackageStep }} - - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force - displayName: Use local NuGet packages - - template: /eng/pipelines/common/templates/steps/update-config-file-step.yml parameters: TCPConnectionString: $(SQL_TCP_CONN_STRING) diff --git a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml index 02d17dfcec..14e81a66e2 100644 --- a/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml +++ b/eng/pipelines/common/templates/steps/build-all-configurations-signed-dlls-step.yml @@ -28,14 +28,6 @@ steps: # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self - # Configure NuGet to use the local packages/ directory where the Abstractions - # package will be. - - pwsh: New-Item -Path "$(packageFolderName)" -ItemType Directory -Force - displayName: Create packages/ directory - - - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force - displayName: Use local NuGet packages - - task: MSBuild@1 displayName: 'BuildAllConfigurations using build.proj' inputs: diff --git a/eng/pipelines/common/templates/steps/override-sni-version.yml b/eng/pipelines/common/templates/steps/override-sni-version.yml index e3754b40b7..3b275262c3 100644 --- a/eng/pipelines/common/templates/steps/override-sni-version.yml +++ b/eng/pipelines/common/templates/steps/override-sni-version.yml @@ -22,26 +22,26 @@ steps: # define file to update $NugetCfg = Join-Path -Path '.' -ChildPath 'NuGet.config' type $NugetCfg - + # load content of xml from file defined above $xml = New-Object XML $xml.Load($NugetCfg) - + # define namespace used to read a node $nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable) $nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI) - + # get the package sources node $packageSources = $xml.SelectSingleNode('//ns:packageSources', $nsm) - + # define new package source $newSource = $xml.CreateElement("add") $newSource.SetAttribute("key","SNIValidation") $newSource.SetAttribute("value","${{parameters.SNIValidationFeed}}") - + # add the new package source $packageSources.AppendChild($newSource) - + # save the xml file $xml.Save($NugetCfg) type $NugetCfg @@ -49,6 +49,9 @@ steps: displayName: Update SNI Version in Versions.props inputs: targetType: inline + # TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/42204): + # Package dependency versions have moved to Directory.Packages.props, so the below script no + # longer functions. script: | Write-Host "SNI Version to test = ${{parameters.SNIVersion}}" @@ -60,7 +63,7 @@ steps: $xml = New-Object XML $xml.Load($PropsPath) - # define namespace used to read a node + # define namespace used to read a node $nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable) $nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI) diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index d765707508..7c938e037e 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -226,12 +226,6 @@ stages: - pwsh: 'Get-ChildItem env: | Sort-Object Name' displayName: '[Debug] List Environment Variables' - - ${{if eq(parameters.referenceType, 'Package')}}: - - pwsh: New-Item -Path "$(Build.SourcesDirectory)/packages" -ItemType Directory -Force - displayName: Create packages/ directory - - pwsh: Copy-Item -Path "$(Build.SourcesDirectory)/NuGet.config.local" -Destination "$(Build.SourcesDirectory)/NuGet.config" -Force - displayName: Use local NuGet packages - # Include the code coverage job if the build type is Project. ${{ if eq(parameters.referenceType, 'Project') }}: # Jobs to run as part of the tests stage, after the tests are done. diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index ebb90afbb9..41e3317732 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -45,7 +45,7 @@ parameters: - name: debug type: boolean default: false - + # The list of upstream jobs to depend on. - name: dependsOn type: object @@ -138,20 +138,14 @@ jobs: - pwsh: 'Get-ChildItem Env: | Sort-Object Name' displayName: '[Debug] Print Environment Variables' - # We have a few extra steps for Package reference builds. + # For Package reference builds, we must first download the Abstractions package artifacts. - ${{ if eq(parameters.referenceType, 'Package') }}: - - # Download the Abstractions package artifacts into packages/. - task: DownloadPipelineArtifact@2 displayName: Download Abstractions Package Artifacts inputs: artifactName: ${{ parameters.abstractionsArtifactsName }} targetPath: $(Build.SourcesDirectory)/packages - # Use the local NuGet.config that references the packages/ directory. - - pwsh: cp $(Build.SourcesDirectory)/NuGet.config.local $(Build.SourcesDirectory)/NuGet.config - displayName: Use local NuGet.config - # Install the .NET SDK. - template: /eng/pipelines/steps/install-dotnet.yml@self parameters: diff --git a/eng/pipelines/jobs/stress-tests-ci-job.yml b/eng/pipelines/jobs/stress-tests-ci-job.yml index 868684eff4..53de7e407f 100644 --- a/eng/pipelines/jobs/stress-tests-ci-job.yml +++ b/eng/pipelines/jobs/stress-tests-ci-job.yml @@ -140,8 +140,6 @@ jobs: displayName: Download MDS Artifacts inputs: artifactName: ${{ parameters.mdsArtifactsName }} - # The stress tests solution has a NuGet.config file that configures - # sources to look in this packages/ directory. targetPath: $(Build.SourcesDirectory)/packages # Setup the local SQL Server. diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index a67f4f1652..15bcad98bd 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -198,10 +198,6 @@ jobs: artifactName: ${{ parameters.mdsArtifactsName }} targetPath: $(Build.SourcesDirectory)/packages - # Use the local NuGet.config that references the packages/ directory. - - pwsh: cp $(Build.SourcesDirectory)/NuGet.config.local $(Build.SourcesDirectory)/NuGet.config - displayName: Use local NuGet.config - # Install the .NET SDK and Runtimes. - template: /eng/pipelines/steps/install-dotnet.yml@self parameters: diff --git a/packages/.gitkeep b/packages/.gitkeep new file mode 100644 index 0000000000..a8c0afd785 --- /dev/null +++ b/packages/.gitkeep @@ -0,0 +1,2 @@ +# This directory must exist in order for NuGet restores to succeed prior to # our builds generating +# any NuGet package files here. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs index 4c8d6c1cac..ffc411b8c0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs @@ -2719,6 +2719,7 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) switch (ConnectionOptions.Authentication) { case SqlAuthenticationMethod.ActiveDirectoryIntegrated: + #if NET // In some scenarios for .NET Core, MSAL cannot detect the current user and needs it passed in // for Integrated auth. Allow the user/application to pass it in to work around those scenarios. if (!string.IsNullOrEmpty(ConnectionOptions.UserID)) @@ -2730,6 +2731,9 @@ private SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; } + #else + username = TdsEnums.NTAUTHORITYANONYMOUSLOGON; + #endif if (_activeDirectoryAuthTimeoutRetryHelper.State == ActiveDirectoryAuthenticationTimeoutRetryState.Retrying) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index f574efd2cb..1bf236c348 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -250,12 +250,26 @@ private SqlAuthenticationProviderManager(SqlAuthenticationProviderConfigurationS } } + /// + /// Get an authentication provider by method. + /// + /// Authentication method. + /// Authentication provider or null if not found. internal static SqlAuthenticationProvider? GetProvider(SqlAuthenticationMethod authenticationMethod) { SqlAuthenticationProvider? value; return Instance._providers.TryGetValue(authenticationMethod, out value) ? value : null; } + /// + /// Set an authentication provider by method. + /// + /// Authentication method. + /// Authentication provider. + /// + /// True if succeeded, false on any errors or if the authentication method has already + /// been claimed via app configuration. + /// internal static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider) { if (!provider.IsSupported(authenticationMethod)) @@ -263,16 +277,13 @@ internal static bool SetProvider(SqlAuthenticationMethod authenticationMethod, S throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name); } var methodName = "SetProvider"; - foreach (SqlAuthenticationMethod candidateMethod in Instance._authenticationsWithAppSpecifiedProvider) + if (Instance._authenticationsWithAppSpecifiedProvider.Contains(authenticationMethod)) { - if (candidateMethod == authenticationMethod) - { - Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); + Instance._sqlAuthLogger.LogError(nameof(SqlAuthenticationProviderManager), methodName, $"Failed to add provider {GetProviderType(provider)} because a user-defined provider with type {GetProviderType(Instance._providers[authenticationMethod])} already existed for authentication {authenticationMethod}."); - // The app has already specified a Provider for this - // authentication method, so we won't override it. - return false; - } + // The app has already specified a Provider for this + // authentication method, so we won't override it. + return false; } Instance._providers.AddOrUpdate( authenticationMethod, diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs index ecc9d8fa0f..0d276624c9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Threading.Tasks; using Microsoft.Data.SqlClient.FunctionalTests.DataCommon; using Xunit; @@ -21,15 +23,53 @@ public void DefaultAuthenticationProviders_AppConfig() Assert.IsType( SqlAuthenticationProvider.GetProvider( SqlAuthenticationMethod.ActiveDirectoryInteractive)); - + // There should be no provider for other methods. Spot-check a few. Assert.Null(SqlAuthenticationProvider.GetProvider( #pragma warning disable CS0618 // Type or member is obsolete SqlAuthenticationMethod.ActiveDirectoryPassword)); #pragma warning restore CS0618 // Type or member is obsolete - + Assert.Null(SqlAuthenticationProvider.GetProvider( SqlAuthenticationMethod.ActiveDirectoryManagedIdentity)); } + + // Verify that the dummy provider installed via app.config cannot be replaced. + [ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))] + public void DefaultAuthenticationProviders_NoReplace() + { + // The provider for ActiveDirectoryInteractive should be our dummy + // provider. + Assert.IsType( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + + // Try to add another provider for ActiveDirectoryInteractive. + bool setResult = SqlAuthenticationProvider.SetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive, + new TestProvider()); + + // The set should have failed. + Assert.False(setResult); + + // The dummy provider is still installed. + Assert.IsType( + SqlAuthenticationProvider.GetProvider( + SqlAuthenticationMethod.ActiveDirectoryInteractive)); + } + + private class TestProvider : SqlAuthenticationProvider + { + public override async Task AcquireTokenAsync( + SqlAuthenticationParameters parameters) + { + throw new NotImplementedException(); + } + + public override bool IsSupported(SqlAuthenticationMethod authenticationMethod) + { + return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive; + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config b/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config deleted file mode 100644 index 19c2531f5d..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/StressTests/NuGet.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - From 406aad1c4eaaa617cd59144cd6dfe2abb4c34829 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:28:06 -0400 Subject: [PATCH 11/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Addressed Copilot feedback. --- packages/.gitkeep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/.gitkeep b/packages/.gitkeep index a8c0afd785..43141d722d 100644 --- a/packages/.gitkeep +++ b/packages/.gitkeep @@ -1,2 +1,2 @@ -# This directory must exist in order for NuGet restores to succeed prior to # our builds generating +# This directory must exist in order for NuGet restores to succeed prior to our builds generating # any NuGet package files here. From 568522976838d31799352f611df2bf7200f24318 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:35:05 -0400 Subject: [PATCH 12/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Addressed Copilot review comments. --- eng/pipelines/common/templates/jobs/build-signed-package-job.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml index 9e05c706e0..bcaa6a6673 100644 --- a/eng/pipelines/common/templates/jobs/build-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/build-signed-package-job.yml @@ -73,6 +73,7 @@ jobs: nuspecPath: $(nuspecPath) outputDirectory: $(artifactDirectory) packageVersion: $(mdsPackageVersion) + properties: 'AbstractionsPackageVersion=$(abstractionsPackageVersion)' referenceType: Package - template: /eng/pipelines/common/templates/steps/esrp-code-signing-step.yml@self From df540b1ab9b2423d35cc4c2b5460764e2974da8e Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:17:47 -0400 Subject: [PATCH 13/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Now using reference type to choose NuGet config file. --- NuGet.config | 7 ------ NuGet.config.local | 25 +++++++++++++++++++ build.proj | 11 +++++--- .../templates/steps/run-all-tests-step.yml | 4 +++ .../jobs/pack-azure-package-ci-job.yml | 8 ++++++ .../jobs/test-azure-package-ci-job.yml | 8 ++++++ .../stages/stress-tests-ci-stage.yml | 1 + 7 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 NuGet.config.local diff --git a/NuGet.config b/NuGet.config index 919c0dc628..89a5578c7d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -14,12 +14,5 @@ --> - - - diff --git a/NuGet.config.local b/NuGet.config.local new file mode 100644 index 0000000000..919c0dc628 --- /dev/null +++ b/NuGet.config.local @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/build.proj b/build.proj index 505b2d0c3b..703e92bfbb 100644 --- a/build.proj +++ b/build.proj @@ -10,7 +10,10 @@ false - src\NuGet.config + + $(RepoRoot)/NuGet.config + $(NuGetConfigFile).local + Debug AnyCPU @@ -29,7 +32,7 @@ $(TF) true - Configuration=$(Configuration);ReferenceType=$(ReferenceType) + Configuration=$(Configuration);ReferenceType=$(ReferenceType);RestoreConfigFile=$(NuGetConfigFile) $(CommonProperties);AssemblyVersion=$(SqlServerAssemblyVersion);AssemblyFileVersion=$(SqlServerAssemblyFileVersion);Version=$(SqlServerPackageVersion); $(CommonProperties);AssemblyFileVersion=$(AssemblyFileVersion);TargetsWindows=$(TargetsWindows);TargetsUnix=$(TargetsUnix); $(ProjectProperties);BuildForRelease=false;TargetNetCoreVersion=$(TargetNetCoreVersion);TargetNetFxVersion=$(TargetNetFxVersion) @@ -462,7 +465,7 @@ - + $(CI);TestTargetOS=$(TestOS)netfx;Platform=AnyCPU;$(ProjectProperties);$(NugetPackProperties) @@ -510,7 +513,7 @@ - + diff --git a/eng/pipelines/common/templates/steps/run-all-tests-step.yml b/eng/pipelines/common/templates/steps/run-all-tests-step.yml index f4b26cab1c..1beaa5d713 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -75,10 +75,12 @@ steps: msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} ${{ else }}: # x86 msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} - task: MSBuild@1 @@ -93,12 +95,14 @@ steps: msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false ${{ else }}: # x86 msbuildArguments: >- -t:RunUnitTests -p:TF=${{ parameters.targetFramework }} + -p:ReferenceType=${{ parameters.referenceType }} -p:DotnetPath=${{ parameters.dotnetx86RootPath }} -p:Filter="category=flaky" -p:CollectCodeCoverage=false diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index 41e3317732..ed97af3c5b 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -95,10 +95,18 @@ jobs: - name: dotnetPackagesDir value: $(Build.StagingDirectory)/dotnetPackages + # The NuGet.config file to use for restoring packages. + - name: nugetConfigFile + ${{ if eq(parameters.referenceType, 'Package') }}: + value: $(Build.SourcesDirectory)/NuGet.config.local + ${{ else }}: + value: $(Build.SourcesDirectory)/NuGet.config + # dotnet CLI arguments common to all commands. - name: commonArguments value: >- --verbosity ${{ parameters.dotnetVerbosity }} + --configfile $(nugetConfigFile) -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 15bcad98bd..139fa17716 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -136,10 +136,18 @@ jobs: - name: project value: src/Microsoft.Data.SqlClient.Extensions/Azure/test/Azure.Test.csproj + # The NuGet.config file to use for restoring packages. + - name: nugetConfigFile + ${{ if eq(parameters.referenceType, 'Package') }}: + value: $(Build.SourcesDirectory)/NuGet.config.local + ${{ else }}: + value: $(Build.SourcesDirectory)/NuGet.config + # dotnet CLI arguments common to all commands. - name: commonArguments value: >- --verbosity ${{ parameters.dotnetVerbosity }} + --configfile $(nugetConfigFile) -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml index 22905f23de..51319dc514 100644 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -95,6 +95,7 @@ stages: value: >- --verbosity ${{parameters.dotnetVerbosity}} --artifacts-path $(dotnetArtifactsDir) + --configfile=$(Build.SourcesDirectory)/NuGet.config.local -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} -p:AzurePackageVersion=${{parameters.azurePackageVersion}} From ef0a19448a4d3d97860cfb480d8c695be6a6d9f0 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:38:03 -0400 Subject: [PATCH 14/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Fixed Azure build command lines. --- eng/pipelines/jobs/pack-azure-package-ci-job.yml | 3 +-- eng/pipelines/jobs/test-azure-package-ci-job.yml | 3 +-- eng/pipelines/stages/stress-tests-ci-stage.yml | 7 +++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/eng/pipelines/jobs/pack-azure-package-ci-job.yml b/eng/pipelines/jobs/pack-azure-package-ci-job.yml index ed97af3c5b..33169bbe03 100644 --- a/eng/pipelines/jobs/pack-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/pack-azure-package-ci-job.yml @@ -106,7 +106,6 @@ jobs: - name: commonArguments value: >- --verbosity ${{ parameters.dotnetVerbosity }} - --configfile $(nugetConfigFile) -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} @@ -169,7 +168,7 @@ jobs: command: custom custom: restore projects: $(project) - arguments: $(commonArguments) + arguments: $(commonArguments) --configfile $(nugetConfigFile) # Build the project. - task: DotNetCoreCLI@2 diff --git a/eng/pipelines/jobs/test-azure-package-ci-job.yml b/eng/pipelines/jobs/test-azure-package-ci-job.yml index 139fa17716..2d2da14435 100644 --- a/eng/pipelines/jobs/test-azure-package-ci-job.yml +++ b/eng/pipelines/jobs/test-azure-package-ci-job.yml @@ -147,7 +147,6 @@ jobs: - name: commonArguments value: >- --verbosity ${{ parameters.dotnetVerbosity }} - --configfile $(nugetConfigFile) -p:ReferenceType=${{ parameters.referenceType }} -p:AbstractionsPackageVersion=${{ parameters.abstractionsPackageVersion }} -p:MdsPackageVersion=${{ parameters.mdsPackageVersion }} @@ -261,7 +260,7 @@ jobs: command: custom custom: restore projects: $(project) - arguments: $(commonArguments) + arguments: $(commonArguments) --configfile $(nugetConfigFile) # Build the project. - task: DotNetCoreCLI@2 diff --git a/eng/pipelines/stages/stress-tests-ci-stage.yml b/eng/pipelines/stages/stress-tests-ci-stage.yml index 51319dc514..d1eae30ff4 100644 --- a/eng/pipelines/stages/stress-tests-ci-stage.yml +++ b/eng/pipelines/stages/stress-tests-ci-stage.yml @@ -95,7 +95,6 @@ stages: value: >- --verbosity ${{parameters.dotnetVerbosity}} --artifacts-path $(dotnetArtifactsDir) - --configfile=$(Build.SourcesDirectory)/NuGet.config.local -p:MdsPackageVersion=${{parameters.mdsPackageVersion}} -p:AzurePackageVersion=${{parameters.azurePackageVersion}} @@ -141,7 +140,7 @@ stages: mdsArtifactsName: ${{ parameters.mdsArtifactsName }} solution: $(solution) testProject: $(testProject) - restoreArguments: $(commonArguments) + restoreArguments: $(commonArguments) --configfile=$(Build.SourcesDirectory)/NuGet.config.local buildArguments: $(buildArguments) netTestRuntimes: ${{ parameters.netTestRuntimes }} configContent: $(ConfigContent) @@ -161,7 +160,7 @@ stages: mdsArtifactsName: ${{ parameters.mdsArtifactsName }} solution: $(solution) testProject: $(testProject) - restoreArguments: $(commonArguments) + restoreArguments: $(commonArguments) --configfile=$(Build.SourcesDirectory)/NuGet.config.local buildArguments: $(buildArguments) netTestRuntimes: ${{ parameters.netTestRuntimes }} # Note that we include the .NET Framework runtimes for test runs on @@ -184,7 +183,7 @@ stages: mdsArtifactsName: ${{ parameters.mdsArtifactsName }} solution: $(solution) testProject: $(testProject) - restoreArguments: $(commonArguments) + restoreArguments: $(commonArguments) --configfile=$(Build.SourcesDirectory)/NuGet.config.local buildArguments: $(buildArguments) netTestRuntimes: ${{ parameters.netTestRuntimes }} configContent: $(ConfigContent) From 4f9c47b28953d82e5afc2b60d12bd03e485ac01f Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:37:05 -0400 Subject: [PATCH 15/15] Task 41737: Merge feat/azure-split to main: Step 3 - Tie everything together - Added missing reference type to AKV restore targets. --- build.proj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.proj b/build.proj index 703e92bfbb..13828ed890 100644 --- a/build.proj +++ b/build.proj @@ -493,7 +493,7 @@ - + @@ -503,7 +503,7 @@ - +