Skip to content

Commit 8852545

Browse files
Fix | Handle NRE on Azure federated authentication (dotnet#1625)
# Conflicts: # src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs # src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx # src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
1 parent be9731c commit 8852545

File tree

9 files changed

+91
-41
lines changed

9 files changed

+91
-41
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

+12-17
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ public void AssertUnrecoverableStateCountIsCorrect()
103103

104104
internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
105105
{
106+
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600
107+
internal const int MsalHttpRetryStatusCode = 429;
108+
106109
// CONNECTION AND STATE VARIABLES
107110
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
108111
private TdsParser _parser;
@@ -2421,7 +2424,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
24212424
// Deal with Msal service exceptions first, retry if 429 received.
24222425
catch (MsalServiceException serviceException)
24232426
{
2424-
if (429 == serviceException.StatusCode)
2427+
if (serviceException.StatusCode == MsalHttpRetryStatusCode)
24252428
{
24262429
RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter;
24272430
if (retryAfter.Delta.HasValue)
@@ -2440,9 +2443,15 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
24402443
}
24412444
else
24422445
{
2443-
break;
2446+
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException error:> Timeout: {0}", serviceException.ErrorCode);
2447+
throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException);
24442448
}
24452449
}
2450+
else
2451+
{
2452+
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException error:> {0}", serviceException.ErrorCode);
2453+
throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username);
2454+
}
24462455
}
24472456
// Deal with normal MsalExceptions.
24482457
catch (MsalException msalException)
@@ -2453,21 +2462,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
24532462
{
24542463
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MSALException error:> {0}", msalException.ErrorCode);
24552464

2456-
// Error[0]
2457-
SqlErrorCollection sqlErs = new();
2458-
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));
2459-
2460-
// Error[1]
2461-
string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode);
2462-
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
2463-
2464-
// Error[2]
2465-
if (!string.IsNullOrEmpty(msalException.Message))
2466-
{
2467-
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, msalException.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
2468-
}
2469-
SqlException exc = SqlException.CreateException(sqlErs, "", this);
2470-
throw exc;
2465+
throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username);
24712466
}
24722467

24732468
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval);

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUtil.cs

+4
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,10 @@ internal static Exception ActiveDirectoryDeviceFlowTimeout()
510510
return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication);
511511
}
512512

513+
internal static Exception ActiveDirectoryTokenRetrievingTimeout(string authenticaton, string errorCode, Exception exception)
514+
{
515+
return ADP.TimeoutException(StringsHelper.GetString(Strings.AAD_Token_Retrieving_Timeout, authenticaton, errorCode, exception?.Message), exception);
516+
}
513517

514518
//
515519
// SQL.DataCommand

src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs

+11-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1932,4 +1932,7 @@
19321932
<data name="SQL_ParameterDirectionInvalidForOptimizedBinding" xml:space="preserve">
19331933
<value>Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.</value>
19341934
</data>
1935-
</root>
1935+
<data name="AAD_Token_Retrieving_Timeout" xml:space="preserve">
1936+
<value>Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}</value>
1937+
</data>
1938+
</root>

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

+11-17
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public void AssertUnrecoverableStateCountIsCorrect()
105105

106106
sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
107107
{
108+
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600
109+
internal const int MsalHttpRetryStatusCode = 429;
108110

109111
// Connection re-route limit
110112
internal const int _maxNumberOfRedirectRoute = 10;
@@ -2859,7 +2861,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
28592861
// Deal with Msal service exceptions first, retry if 429 received.
28602862
catch (MsalServiceException serviceException)
28612863
{
2862-
if (429 == serviceException.StatusCode)
2864+
if (serviceException.StatusCode == MsalHttpRetryStatusCode)
28632865
{
28642866
RetryConditionHeaderValue retryAfter = serviceException.Headers.RetryAfter;
28652867
if (retryAfter.Delta.HasValue)
@@ -2878,9 +2880,15 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
28782880
}
28792881
else
28802882
{
2881-
break;
2883+
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException error:> Timeout: {0}", serviceException.ErrorCode);
2884+
throw SQL.ActiveDirectoryTokenRetrievingTimeout(Enum.GetName(typeof(SqlAuthenticationMethod), ConnectionOptions.Authentication), serviceException.ErrorCode, serviceException);
28822885
}
28832886
}
2887+
else
2888+
{
2889+
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MsalServiceException error:> {0}", serviceException.ErrorCode);
2890+
throw ADP.CreateSqlException(serviceException, ConnectionOptions, this, username);
2891+
}
28842892
}
28852893
// Deal with normal MsalExceptions.
28862894
catch (MsalException msalException)
@@ -2891,21 +2899,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
28912899
{
28922900
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken.MSALException error:> {0}", msalException.ErrorCode);
28932901

2894-
// Error[0]
2895-
SqlErrorCollection sqlErs = new SqlErrorCollection();
2896-
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));
2897-
2898-
// Error[1]
2899-
string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode);
2900-
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
2901-
2902-
// Error[2]
2903-
if (!string.IsNullOrEmpty(msalException.Message))
2904-
{
2905-
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, msalException.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
2906-
}
2907-
SqlException exc = SqlException.CreateException(sqlErs, "", this);
2908-
throw exc;
2902+
throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username);
29092903
}
29102904

29112905
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval);

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUtil.cs

+5
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,11 @@ static internal Exception ActiveDirectoryDeviceFlowTimeout()
654654
return ADP.TimeoutException(Strings.SQL_Timeout_Active_Directory_DeviceFlow_Authentication);
655655
}
656656

657+
internal static Exception ActiveDirectoryTokenRetrievingTimeout(string authenticaton, string errorCode, Exception exception)
658+
{
659+
return ADP.TimeoutException(StringsHelper.GetString(Strings.AAD_Token_Retrieving_Timeout, authenticaton, errorCode, exception?.Message), exception);
660+
}
661+
657662
//
658663
// SQL.DataCommand
659664
//

src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx

+3
Original file line numberDiff line numberDiff line change
@@ -4617,4 +4617,7 @@
46174617
<data name="SQL_ParameterDirectionInvalidForOptimizedBinding" xml:space="preserve">
46184618
<value>Parameter '{0}' cannot have Direction Output or InputOutput when EnableOptimizedParameterBinding is enabled on the parent command.</value>
46194619
</data>
4620+
<data name="AAD_Token_Retrieving_Timeout" xml:space="preserve">
4621+
<value>Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}</value>
4622+
</data>
46204623
</root>

src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs

+31-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using Microsoft.Data.SqlClient.Server;
2525
using Microsoft.Win32;
2626
using IsolationLevel = System.Data.IsolationLevel;
27+
using Microsoft.Identity.Client;
2728

2829
namespace Microsoft.Data.Common
2930
{
@@ -188,9 +189,9 @@ internal static OverflowException Overflow(string error, Exception inner)
188189
return e;
189190
}
190191

191-
internal static TimeoutException TimeoutException(string error)
192+
internal static TimeoutException TimeoutException(string error, Exception inner = null)
192193
{
193-
TimeoutException e = new(error);
194+
TimeoutException e = new(error, inner);
194195
TraceExceptionAsReturnValue(e);
195196
return e;
196197
}
@@ -390,7 +391,34 @@ internal static ArgumentException InvalidArgumentLength(string argumentName, int
390391
=> Argument(StringsHelper.GetString(Strings.ADP_InvalidArgumentLength, argumentName, limit));
391392

392393
internal static ArgumentException MustBeReadOnly(string argumentName) => Argument(StringsHelper.GetString(Strings.ADP_MustBeReadOnly, argumentName));
393-
#endregion
394+
395+
internal static Exception CreateSqlException(MsalException msalException, SqlConnectionString connectionOptions, SqlInternalConnectionTds sender, string username)
396+
{
397+
// Error[0]
398+
SqlErrorCollection sqlErs = new();
399+
400+
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS,
401+
connectionOptions.DataSource,
402+
StringsHelper.GetString(Strings.SQL_MSALFailure, username, connectionOptions.Authentication.ToString("G")),
403+
ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
404+
405+
// Error[1]
406+
string errorMessage1 = StringsHelper.GetString(Strings.SQL_MSALInnerException, msalException.ErrorCode);
407+
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS,
408+
connectionOptions.DataSource, errorMessage1,
409+
ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
410+
411+
// Error[2]
412+
if (!string.IsNullOrEmpty(msalException.Message))
413+
{
414+
sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS,
415+
connectionOptions.DataSource, msalException.Message,
416+
ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0));
417+
}
418+
return SqlException.CreateException(sqlErs, "", sender);
419+
}
420+
421+
#endregion
394422

395423
#region CommandBuilder, Command, BulkCopy
396424
/// <summary>

0 commit comments

Comments
 (0)