Fix AccessTokenCallback to match AccessToken behavior for TNIR and connection pool keys - #4520
Fix AccessTokenCallback to match AccessToken behavior for TNIR and connection pool keys#4520cheenamalhotra wants to merge 5 commits into
Conversation
On .NET Framework the driver disables Transparent Network IP Resolution by default whenever federated authentication is in use, unless the caller explicitly specified the TransparentNetworkIPResolution keyword. However, ShouldDisableTnir only tested _accessTokenInBytes (SqlConnection.AccessToken) and ignored _accessTokenCallback (SqlConnection.AccessTokenCallback), so the two token-supplying APIs behaved differently. Raised in review discussion on #4493. Changes: * Add SqlConnectionInternal.IsAccessTokenProvided, a single source of truth for "the caller supplied a token, either literally or via a callback", and use it in all three places that previously inlined the field checks (ShouldDisableTnir plus two spots in TdsParser.ConsumePreLoginHandshake). The duplicated, hand-written expression is what allowed the two paths to drift apart. * Fix the AccessToken, AccessTokenCallback and SspiContextProvider setters, which each rebuilt the ConnectionPoolKey with the sibling authentication values hard-coded to null. Setting SspiContextProvider silently dropped a previously assigned access token or callback from the pool key, so it never reached the internal connection even though the public property still reported it as set. These now preserve sibling state, matching the ConnectionString setter. (AccessToken and AccessTokenCallback are already mutually exclusive, so that pairing was benign; SspiContextProvider is not.) * Expose ShouldDisableTnir as internal static so it can be unit tested, and add coverage for the TNIR decision matrix and for pool-key preservation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5368f578-219b-40a6-92a9-4742b56edbe6
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR tightens behavior around federated authentication state by ensuring access-token state is consistently represented in connection pooling keys and by unifying “caller-supplied token” detection for TNIR and pre-login logic.
Changes:
- Preserve
AccessToken/AccessTokenCallback/SspiContextProviderinConnectionPoolKeyupdates to avoid silently dropping authentication state. - Introduce
SqlConnectionInternal.IsAccessTokenProvidedand apply it in pre-login and certificate validation checks. - Add NETFRAMEWORK TNIR tests and simulated server tests covering token/callback state and mutual exclusivity.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs | Adds regression tests for pool-key preservation and token/callback mutual exclusivity. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionOptionsTest.cs | Adds NETFRAMEWORK theory coverage for TNIR disablement with caller-supplied tokens. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs | Switches token detection to unified IsAccessTokenProvided. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs | Updates pool-key rebuilds to preserve other auth-related properties. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs | Adds IsAccessTokenProvided and refactors NETFRAMEWORK TNIR logic to accept it as an input. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
* Fully qualify the SqlConnection crefs on ShouldDisableTnir so they match the form already used on IsAccessTokenProvided. * Rename TestShouldDisableTnirWithAccessToken to TestShouldDisableTnirWithCallerSuppliedToken, since the parameter is isAccessTokenProvided and the case covers both AccessToken and AccessTokenCallback. * Assign a real non-null SspiContextProvider in the pool-key test instead of null, via a minimal TestSspiContextProvider stub, so the test matches its name and exercises the setter the way callers actually do. * Add SspiContextProviderIsPreservedInPoolKeyWhenAccessTokenStateIsSet, the reciprocal case: assigning a token must not drop a configured SspiContextProvider from the pool key. Both pool-key tests were verified to fail when the SqlConnection.cs fix is reverted. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5368f578-219b-40a6-92a9-4742b56edbe6
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4520 +/- ##
==========================================
- Coverage 64.73% 62.66% -2.08%
==========================================
Files 288 283 -5
Lines 44088 67057 +22969
==========================================
+ Hits 28542 42020 +13478
- Misses 15546 25037 +9491
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| _connectionString, | ||
| credential: _credential, | ||
| accessToken: _accessToken, | ||
| accessTokenCallback: _accessTokenCallback, |
There was a problem hiding this comment.
SSPI is an alternative to token auth. We shouldn't preserve access token or callback. The miss was that it should have been treated as mutually exclusive and thrown an exception if either of the others was already set.
Follow-up to the review discussion on #4493, where it was noted that the TNIR behavior documented there does not actually apply when
SqlConnection.AccessTokenCallbackis used:The bug
On .NET Framework the driver disables Transparent Network IP Resolution by default whenever federated authentication is in use, unless the caller explicitly specified the
TransparentNetworkIPResolutionkeyword.SqlConnectionInternal.ShouldDisableTnironly tested_accessTokenInBytes(SqlConnection.AccessToken) and ignored_accessTokenCallback(SqlConnection.AccessTokenCallback), so the two token-supplying APIs behaved differently for no good reason.Changes
1. Single source of truth for "a token was supplied."
Added
SqlConnectionInternal.IsAccessTokenProvidedand used it in all three places that previously inlined the field checks —ShouldDisableTnirplus two spots inTdsParser.ConsumePreLoginHandshake. The duplicated hand-written expression is exactly what let these paths drift apart, and the existing@TODOinOnFedAuthInfopredicted this ("we're gonna forget one in one spot and cause a big ol bug someday").2. Pool key no longer drops sibling authentication state.
The
AccessToken,AccessTokenCallbackandSspiContextProvidersetters each rebuilt theConnectionPoolKeywith the sibling authentication values hard-coded tonull. SettingSspiContextProvidersilently dropped a previously assigned access token or callback from the pool key, so it never reached the internal connection even though the public property still reported it as set — which would also have defeated the TNIR fix above. These now preserve sibling state, matching theConnectionStringsetter, which already did this correctly.AccessTokenandAccessTokenCallbackare already mutually exclusive (validated inCheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken*), so that pairing was benign;SspiContextProvideris not mutually exclusive with either, so that one was a live defect.3. Tests.
ShouldDisableTniris nowinternal staticso the decision matrix can be unit tested directly (constructing aSqlConnectionInternalin a unit test is impractical). Added:SqlConnectionOptionsTest.TestShouldDisableTnirWithAccessToken(netfx only) — token/no-token x Azure/non-Azure endpoint x explicit/absent TNIR keyword.ConnectionTests.AccessTokenStateIsPreservedInPoolKeyWhenSspiContextProviderIsSet— verified to fail without theSqlConnection.cschange.ConnectionTests.AccessTokenAndAccessTokenCallbackAreMutuallyExclusive— pins the invariant that makes the token pairing safe.Compatibility
Behavior change is limited to .NET Framework, and only for connections using
AccessTokenCallback, which now get the same TNIR default asAccessToken. Users who explicitly setTransparentNetworkIPResolutionin the connection string are unaffected — the explicit keyword still takes precedence, so the escape hatch documented in #4493 continues to work.Checklist
Suggested release note
Fixed
SqlConnection.AccessTokenCallbacknot disabling Transparent Network IP Resolution by default on .NET Framework, making it consistent withSqlConnection.AccessToken. Also fixed theAccessToken,AccessTokenCallbackandSspiContextProvidersetters discarding each other's values from the connection pool key.Notes for reviewers
dotnet/SqlClientso the full CI/ADO pipeline suite runs.