-
Notifications
You must be signed in to change notification settings - Fork 317
Fix | Fix driver to not send expired token and refresh token first before sending it. #2273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DavoudEshtehari
merged 15 commits into
dotnet:main
from
arellegue:FixDriverSendingExpiredToken
Jan 24, 2024
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e11712e
Fixed token refreshing logic and added unit test.
arellegue d474331
Use DataTestUtility.AADPasswordConnectionString.
arellegue 7dcd060
Added ITestOutputHelper so can see some logs in pipeline.
arellegue 2e7a224
Use SqlCommand type instead of var.
arellegue 7756df0
Add a wrapper for _output, LogInfo, since ITestOutputHelper can not b…
arellegue 67ab661
Remove ITestOutputHelper as test is failing because of it.
arellegue f8cf666
Removed misplaced ITestOutputHelper output parameter declaration.
arellegue af57c8e
Use a more meaningful variable names.
arellegue 79b3483
Only add AADFedAuthTokenRefreshTest to test group 3.
arellegue 6bc021e
Removed commented out Console.Writeline.
arellegue bcb4936
Add meaningful comment to trigger build in pipeline.
arellegue fa913b1
Refactor as suggested by creating hekper class for Federated Authenti…
arellegue 96462af
Change in range test to use Assert.InRange as suggested.
arellegue 25d2626
Removed unnecessary comment.
arellegue 0950949
Apply suggestions from code review
DavoudEshtehari File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
....SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // 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; | ||
DavoudEshtehari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.Common.SystemDataInternals; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.ManualTesting.Tests | ||
| { | ||
| public class AADFedAuthTokenRefreshTest | ||
| { | ||
| private readonly ITestOutputHelper _testOutputHelper; | ||
|
|
||
| public AADFedAuthTokenRefreshTest(ITestOutputHelper testOutputHelper) | ||
| { | ||
| _testOutputHelper = testOutputHelper; | ||
| } | ||
|
|
||
| [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAADPasswordConnStrSetup))] | ||
| public void FedAuthTokenRefreshTest() | ||
| { | ||
| string connectionString = DataTestUtility.AADPasswordConnectionString; | ||
|
|
||
| using (SqlConnection connection = new SqlConnection(connectionString)) | ||
| { | ||
| connection.Open(); | ||
|
|
||
| string oldTokenHash = ""; | ||
| DateTime? oldExpiryDateTime = FedAuthTokenHelper.SetTokenExpiryDateTime(connection, minutesToExpire: 1, out oldTokenHash); | ||
| Assert.True(oldExpiryDateTime != null, "Failed to make token expiry to expire in one minute."); | ||
|
|
||
| // Convert and display the old expiry into local time which should be in 1 minute from now | ||
| DateTime oldLocalExpiryTime = TimeZoneInfo.ConvertTimeFromUtc((DateTime)oldExpiryDateTime, TimeZoneInfo.Local); | ||
| LogInfo($"Token: {oldTokenHash} Old Expiry: {oldLocalExpiryTime}"); | ||
| TimeSpan timeDiff = oldLocalExpiryTime - DateTime.Now; | ||
| Assert.InRange(timeDiff.TotalSeconds, 0, 60); | ||
|
|
||
| // Check if connection is still alive to continue further testing | ||
| string result = ""; | ||
| SqlCommand cmd = connection.CreateCommand(); | ||
| cmd.CommandText = "select @@version"; | ||
| result = $"{cmd.ExecuteScalar()}"; | ||
| Assert.True(result != string.Empty, "The connection's command must return a value"); | ||
|
|
||
| // The new connection will use the same FedAuthToken but will refresh it first as it will expire in 1 minute. | ||
| using (SqlConnection connection2 = new SqlConnection(connectionString)) | ||
| { | ||
| connection2.Open(); | ||
|
|
||
| // Check if connection is alive | ||
| cmd = connection2.CreateCommand(); | ||
| cmd.CommandText = "select 1"; | ||
| result = $"{cmd.ExecuteScalar()}"; | ||
| Assert.True(result != string.Empty, "The connection's command must return a value after a token refresh."); | ||
|
|
||
| string newTokenHash = ""; | ||
| DateTime? newExpiryDateTime = FedAuthTokenHelper.GetTokenExpiryDateTime(connection2, out newTokenHash); | ||
| DateTime newLocalExpiryTime = TimeZoneInfo.ConvertTimeFromUtc((DateTime)newExpiryDateTime, TimeZoneInfo.Local); | ||
| LogInfo($"Token: {newTokenHash} New Expiry: {newLocalExpiryTime}"); | ||
|
|
||
| Assert.True(oldTokenHash == newTokenHash, "The token's hash before and after token refresh must be identical."); | ||
| Assert.True(newLocalExpiryTime > oldLocalExpiryTime, "The refreshed token must have a new or later expiry time."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void LogInfo(string message) | ||
| { | ||
| _testOutputHelper.WriteLine(message); | ||
| } | ||
| } | ||
| } | ||
108 changes: 108 additions & 0 deletions
108
...oft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/FedAuthTokenHelper.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| // 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; | ||
DavoudEshtehari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using System.Collections; | ||
| using System.Linq; | ||
| using System.Reflection; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.Common.SystemDataInternals | ||
| { | ||
| internal static class FedAuthTokenHelper | ||
| { | ||
| internal static DateTime? GetTokenExpiryDateTime(SqlConnection connection, out string tokenHash) | ||
| { | ||
| try | ||
| { | ||
| object authenticationContextValueObj = GetAuthenticationContextValue(connection); | ||
|
|
||
| DateTime expirationTimeProperty = (DateTime)authenticationContextValueObj.GetType().GetProperty("ExpirationTime", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(authenticationContextValueObj, null); | ||
|
|
||
| tokenHash = GetTokenHash(authenticationContextValueObj); | ||
|
|
||
| return expirationTimeProperty; | ||
| } | ||
| catch (Exception) | ||
| { | ||
| tokenHash = ""; | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| internal static DateTime? SetTokenExpiryDateTime(SqlConnection connection, int minutesToExpire, out string tokenHash) | ||
| { | ||
| try | ||
| { | ||
| object authenticationContextValueObj = GetAuthenticationContextValue(connection); | ||
|
|
||
| DateTime expirationTimeProperty = (DateTime)authenticationContextValueObj.GetType().GetProperty("ExpirationTime", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(authenticationContextValueObj, null); | ||
|
|
||
| expirationTimeProperty = DateTime.UtcNow.AddMinutes(minutesToExpire); | ||
|
|
||
| FieldInfo expirationTimeInfo = authenticationContextValueObj.GetType().GetField("_expirationTime", BindingFlags.NonPublic | BindingFlags.Instance); | ||
| expirationTimeInfo.SetValue(authenticationContextValueObj, expirationTimeProperty); | ||
|
|
||
| tokenHash = GetTokenHash(authenticationContextValueObj); | ||
|
|
||
| return expirationTimeProperty; | ||
| } | ||
| catch (Exception) | ||
| { | ||
| tokenHash = ""; | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| internal static string GetTokenHash(object authenticationContextValueObj) | ||
| { | ||
| try | ||
| { | ||
| Assembly sqlConnectionAssembly = Assembly.GetAssembly(typeof(SqlConnection)); | ||
|
|
||
| Type sqlFedAuthTokenType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SqlFedAuthToken"); | ||
|
|
||
| Type[] sqlFedAuthTokenTypeArray = new Type[] { sqlFedAuthTokenType }; | ||
|
|
||
| ConstructorInfo sqlFedAuthTokenConstructorInfo = sqlFedAuthTokenType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); | ||
|
|
||
| Type activeDirectoryAuthenticationTimeoutRetryHelperType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.ActiveDirectoryAuthenticationTimeoutRetryHelper"); | ||
|
|
||
| ConstructorInfo activeDirectoryAuthenticationTimeoutRetryHelperConstructorInfo = activeDirectoryAuthenticationTimeoutRetryHelperType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, Type.EmptyTypes, null); | ||
|
|
||
| object activeDirectoryAuthenticationTimeoutRetryHelperObj = activeDirectoryAuthenticationTimeoutRetryHelperConstructorInfo.Invoke(new object[] { }); | ||
|
|
||
| MethodInfo tokenHashInfo = activeDirectoryAuthenticationTimeoutRetryHelperObj.GetType().GetMethod("GetTokenHash", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, sqlFedAuthTokenTypeArray, null); | ||
|
|
||
| byte[] tokenBytes = (byte[])authenticationContextValueObj.GetType().GetProperty("AccessToken", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(authenticationContextValueObj, null); | ||
|
|
||
| object sqlFedAuthTokenObj = sqlFedAuthTokenConstructorInfo.Invoke(new object[] { }); | ||
| FieldInfo accessTokenInfo = sqlFedAuthTokenObj.GetType().GetField("accessToken", BindingFlags.NonPublic | BindingFlags.Instance); | ||
| accessTokenInfo.SetValue(sqlFedAuthTokenObj, tokenBytes); | ||
|
|
||
| string tokenHash = (string)tokenHashInfo.Invoke(activeDirectoryAuthenticationTimeoutRetryHelperObj, new object[] { sqlFedAuthTokenObj }); | ||
|
|
||
| return tokenHash; | ||
| } | ||
| catch (Exception) | ||
| { | ||
| return ""; | ||
| } | ||
| } | ||
|
|
||
| internal static object GetAuthenticationContextValue(SqlConnection connection) | ||
| { | ||
| object innerConnectionObj = connection.GetType().GetProperty("InnerConnection", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(connection); | ||
|
|
||
| object databaseConnectionPoolObj = innerConnectionObj.GetType().GetProperty("Pool", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(innerConnectionObj); | ||
|
|
||
| IEnumerable authenticationContexts = (IEnumerable)databaseConnectionPoolObj.GetType().GetProperty("AuthenticationContexts", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(databaseConnectionPoolObj, null); | ||
|
|
||
| object authenticationContextObj = authenticationContexts.Cast<object>().FirstOrDefault(); | ||
|
|
||
| object authenticationContextValueObj = authenticationContextObj.GetType().GetProperty("Value").GetValue(authenticationContextObj, null); | ||
|
|
||
| return authenticationContextValueObj; | ||
| } | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.