Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 1ae377f

Browse files
author
Keerat Singh
authored
Merge pull request #31039 from AfsanehR/AccessToken2.2
[release/2.2] Added AAD Authentication using Access Token
2 parents 537c094 + c71cd8e commit 1ae377f

File tree

16 files changed

+492
-28
lines changed

16 files changed

+492
-28
lines changed

src/System.Data.SqlClient/ref/System.Data.SqlClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ public void ResetStatistics() { }
495495
public System.Collections.IDictionary RetrieveStatistics() { throw null; }
496496
public static void ChangePassword(string connectionString, string newPassword) { throw null; }
497497
public static void ChangePassword(string connectionString, System.Data.SqlClient.SqlCredential credential, System.Security.SecureString newPassword) { throw null; }
498+
public string AccessToken { get { throw null; } set { } }
498499

499500
}
500501
public sealed partial class SqlConnectionStringBuilder : System.Data.Common.DbConnectionStringBuilder

src/System.Data.SqlClient/src/Resources/Strings.resx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,4 +1296,25 @@
12961296
<data name="SQL_ChangePasswordUseOfUnallowedKey" xml:space="preserve">
12971297
<value>The keyword '{0}' must not be specified in the connectionString argument to ChangePassword.</value>
12981298
</data>
1299+
<data name="SQL_ParsingErrorWithState" xml:space="preserve">
1300+
<value>Internal connection fatal error. Error state: {0}.</value>
1301+
</data>
1302+
<data name="SQL_ParsingErrorValue" xml:space="preserve">
1303+
<value>Internal connection fatal error. Error state: {0}, Value: {1}.</value>
1304+
</data>
1305+
<data name="ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity" xml:space="preserve">
1306+
<value>Cannot set the AccessToken property if the 'Integrated Security' connection string keyword has been set to 'true' or 'SSPI'.</value>
1307+
</data>
1308+
<data name="ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword" xml:space="preserve">
1309+
<value>Cannot set the AccessToken property if 'UserID', 'UID', 'Password', or 'PWD' has been specified in connection string.</value>
1310+
</data>
1311+
<data name="ADP_InvalidMixedUsageOfCredentialAndAccessToken" xml:space="preserve">
1312+
<value>Cannot set the Credential property if the AccessToken property is already set.</value>
1313+
</data>
1314+
<data name="SQL_ParsingErrorFeatureId" xml:space="preserve">
1315+
<value>Internal connection fatal error. Error state: {0}, Feature Id: {1}.</value>
1316+
</data>
1317+
<data name="SQL_ParsingErrorAuthLibraryType" xml:space="preserve">
1318+
<value>Internal connection fatal error. Error state: {0}, Authentication Library Type: {1}.</value>
1319+
</data>
12991320
</root>

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,5 +910,17 @@ internal static ArgumentException InvalidMixedArgumentOfSecureCredentialAndInteg
910910
{
911911
return Argument(SR.GetString(SR.ADP_InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity));
912912
}
913+
internal static InvalidOperationException InvalidMixedUsageOfAccessTokenAndIntegratedSecurity()
914+
{
915+
return ADP.InvalidOperation(SR.GetString(SR.ADP_InvalidMixedUsageOfAccessTokenAndIntegratedSecurity));
916+
}
917+
internal static InvalidOperationException InvalidMixedUsageOfAccessTokenAndUserIDPassword()
918+
{
919+
return ADP.InvalidOperation(SR.GetString(SR.ADP_InvalidMixedUsageOfAccessTokenAndUserIDPassword));
920+
}
921+
internal static Exception InvalidMixedUsageOfCredentialAndAccessToken()
922+
{
923+
return ADP.InvalidOperation(SR.GetString(SR.ADP_InvalidMixedUsageOfCredentialAndAccessToken));
924+
}
913925
}
914926
}

src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnection.cs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public sealed partial class SqlConnection : DbConnection, ICloneable
3434
private SqlCredential _credential;
3535
private string _connectionString;
3636
private int _connectRetryCount;
37+
private string _accessToken; // Access Token to be used for token based authentication
3738

3839
// connection resiliency
3940
private object _reconnectLock = new object();
@@ -97,6 +98,7 @@ private SqlConnection(SqlConnection connection)
9798
_credential = new SqlCredential(connection._credential.UserId, password);
9899
}
99100

101+
_accessToken = connection._accessToken;
100102
CacheConnectionStringProperties();
101103
}
102104

@@ -222,12 +224,19 @@ public override string ConnectionString
222224
}
223225
set
224226
{
225-
if (_credential != null)
227+
if (_credential != null || _accessToken != null)
226228
{
227229
SqlConnectionString connectionOptions = new SqlConnectionString(value);
228-
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions);
230+
if (_credential != null)
231+
{
232+
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions);
233+
}
234+
else
235+
{
236+
CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(connectionOptions);
237+
}
229238
}
230-
ConnectionString_Set(new SqlConnectionPoolKey(value, _credential));
239+
ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken));
231240
_connectionString = value; // Change _connectionString value only after value is validated
232241
CacheConnectionStringProperties();
233242
}
@@ -242,6 +251,37 @@ public override int ConnectionTimeout
242251
}
243252
}
244253

254+
// AccessToken: To be used for token based authentication
255+
public string AccessToken
256+
{
257+
get
258+
{
259+
string result = _accessToken;
260+
// When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true
261+
// otherwise, return null
262+
SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions;
263+
return InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo ? null : _accessToken;
264+
}
265+
set
266+
{
267+
// If a connection is connecting or is ever opened, AccessToken cannot be set
268+
if (!InnerConnection.AllowSetConnectionString)
269+
{
270+
throw ADP.OpenConnectionPropertySet(nameof(AccessToken), InnerConnection.State);
271+
}
272+
273+
if (value != null)
274+
{
275+
// Check if the usage of AccessToken has any conflict with the keys used in connection string and credential
276+
CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions);
277+
}
278+
279+
// Need to call ConnectionString_Set to do proper pool group check
280+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, credential: _credential, accessToken: value));
281+
_accessToken = value;
282+
}
283+
}
284+
245285
public override string Database
246286
{
247287
// if the connection is open, we need to ask the inner connection what it's
@@ -396,12 +436,16 @@ public SqlCredential Credential
396436
if (value != null)
397437
{
398438
CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential((SqlConnectionString)ConnectionOptions);
439+
if (_accessToken != null)
440+
{
441+
throw ADP.InvalidMixedUsageOfCredentialAndAccessToken();
442+
}
399443
}
400444

401445
_credential = value;
402446

403447
// Need to call ConnectionString_Set to do proper pool group check
404-
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential));
448+
ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: _accessToken));
405449
}
406450
}
407451

@@ -422,6 +466,29 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential
422466
}
423467
}
424468

469+
// CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken: check if the usage of AccessToken has any conflict
470+
// with the keys used in connection string and credential
471+
// If there is any conflict, it throws InvalidOperationException
472+
// This is to be used setter of ConnectionString and AccessToken properties
473+
private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(SqlConnectionString connectionOptions)
474+
{
475+
if (UsesClearUserIdOrPassword(connectionOptions))
476+
{
477+
throw ADP.InvalidMixedUsageOfAccessTokenAndUserIDPassword();
478+
}
479+
480+
if (UsesIntegratedSecurity(connectionOptions))
481+
{
482+
throw ADP.InvalidMixedUsageOfAccessTokenAndIntegratedSecurity();
483+
}
484+
485+
// Check if the usage of AccessToken has the conflict with credential
486+
if (_credential != null)
487+
{
488+
throw ADP.InvalidMixedUsageOfCredentialAndAccessToken();
489+
}
490+
}
491+
425492
protected override DbProviderFactory DbProviderFactory
426493
{
427494
get => SqlClientFactory.Instance;
@@ -654,6 +721,7 @@ public override void Close()
654721
private void DisposeMe(bool disposing)
655722
{
656723
_credential = null;
724+
_accessToken = null;
657725

658726
if (!disposing)
659727
{
@@ -1360,7 +1428,7 @@ public static void ChangePassword(string connectionString, string newPassword)
13601428
throw ADP.InvalidArgumentLength(nameof(newPassword), TdsEnums.MAXLEN_NEWPASSWORD);
13611429
}
13621430

1363-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null);
1431+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null);
13641432

13651433
SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
13661434
if (connectionOptions.IntegratedSecurity)
@@ -1403,7 +1471,7 @@ public static void ChangePassword(string connectionString, SqlCredential credent
14031471
throw ADP.InvalidArgumentLength(nameof(newSecurePassword), TdsEnums.MAXLEN_NEWPASSWORD);
14041472
}
14051473

1406-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential);
1474+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null);
14071475

14081476
SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key);
14091477

@@ -1441,7 +1509,7 @@ private static void ChangePassword(string connectionString, SqlConnectionString
14411509
if (con != null)
14421510
con.Dispose();
14431511
}
1444-
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential);
1512+
SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null);
14451513

14461514
SqlConnectionFactory.SingletonInstance.ClearPool(key);
14471515
}

src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnectionFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt
132132
opt = new SqlConnectionString(opt, instanceName, userInstance: false, setEnlistValue: null);
133133
poolGroupProviderInfo = null; // null so we do not pass to constructor below...
134134
}
135-
result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling);
135+
result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken);
136136
return result;
137137
}
138138

src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnectionPoolKey.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//------------------------------------------------------------------------------
88

99
using System.Data.Common;
10+
using System.Diagnostics;
1011

1112
namespace System.Data.SqlClient
1213
{
@@ -16,16 +17,20 @@ internal class SqlConnectionPoolKey : DbConnectionPoolKey
1617
{
1718
private int _hashValue;
1819
private SqlCredential _credential;
20+
private readonly string _accessToken;
1921

20-
internal SqlConnectionPoolKey(string connectionString, SqlCredential credential) : base(connectionString)
22+
internal SqlConnectionPoolKey(string connectionString, SqlCredential credential, string accessToken) : base(connectionString)
2123
{
24+
Debug.Assert(_credential == null || _accessToken == null, "Credential and AccessToken can't have the value at the same time.");
2225
_credential = credential;
26+
_accessToken = accessToken;
2327
CalculateHashCode();
2428
}
2529

2630
private SqlConnectionPoolKey(SqlConnectionPoolKey key) : base(key)
2731
{
2832
_credential = key.Credential;
33+
_accessToken = key.AccessToken;
2934
CalculateHashCode();
3035
}
3136

@@ -50,12 +55,12 @@ internal override string ConnectionString
5055

5156
internal SqlCredential Credential => _credential;
5257

58+
internal string AccessToken => _accessToken;
59+
5360
public override bool Equals(object obj)
5461
{
5562
SqlConnectionPoolKey key = obj as SqlConnectionPoolKey;
56-
return (key != null &&
57-
ConnectionString == key.ConnectionString &&
58-
Credential == key.Credential);
63+
return (key != null && _credential == key._credential && ConnectionString == key.ConnectionString && Object.ReferenceEquals(_accessToken, key._accessToken));
5964
}
6065

6166
public override int GetHashCode()
@@ -74,6 +79,13 @@ private void CalculateHashCode()
7479
_hashValue = _hashValue * 17 + _credential.GetHashCode();
7580
}
7681
}
82+
else if (_accessToken != null)
83+
{
84+
unchecked
85+
{
86+
_hashValue = _hashValue * 17 + _accessToken.GetHashCode();
87+
}
88+
}
7789
}
7890
}
7991
}

0 commit comments

Comments
 (0)