Skip to content

Commit 401a45f

Browse files
committed
CSHARP-603: implemented Sasl authentication support as well as support for new delegated authentication support in server 2.4.
1 parent e0f958e commit 401a45f

File tree

76 files changed

+5206
-1888
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5206
-1888
lines changed

MongoDB.Driver/Communication/MongoConnection.cs

Lines changed: 6 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
1817
using System.IO;
1918
using System.Net.Security;
2019
using System.Net.Sockets;
2120
using System.Security.Cryptography.X509Certificates;
2221
using MongoDB.Bson;
2322
using MongoDB.Bson.IO;
2423
using MongoDB.Bson.Serialization;
24+
using MongoDB.Driver.Communication;
25+
using MongoDB.Driver.Communication.Security;
2526

2627
namespace MongoDB.Driver.Internal
2728
{
@@ -61,16 +62,13 @@ public class MongoConnection
6162
private DateTime _lastUsedAt; // set every time the connection is Released
6263
private int _messageCounter;
6364
private int _requestId;
64-
private Dictionary<string, Authentication> _authentications = new Dictionary<string, Authentication>();
6565

6666
// constructors
6767
internal MongoConnection(MongoConnectionPool connectionPool)
68+
: this(connectionPool.ServerInstance)
6869
{
69-
_serverInstance = connectionPool.ServerInstance;
7070
_connectionPool = connectionPool;
7171
_generationId = connectionPool.GenerationId;
72-
_createdAt = DateTime.UtcNow;
73-
_state = MongoConnectionState.Initial;
7472
}
7573

7674
internal MongoConnection(MongoServerInstance serverInstance)
@@ -147,138 +145,6 @@ public MongoConnectionState State
147145
}
148146

149147
// internal methods
150-
internal void Authenticate(string databaseName, MongoCredentials credentials)
151-
{
152-
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
153-
lock (_connectionLock)
154-
{
155-
var nonceCommand = new CommandDocument("getnonce", 1);
156-
var commandResult = RunCommand(databaseName, QueryFlags.None, nonceCommand, false);
157-
if (!commandResult.Ok)
158-
{
159-
throw new MongoAuthenticationException(
160-
"Error getting nonce for authentication.",
161-
new MongoCommandException(commandResult));
162-
}
163-
164-
var nonce = commandResult.Response["nonce"].AsString;
165-
var passwordDigest = MongoUtils.Hash(credentials.Username + ":mongo:" + credentials.Password);
166-
var digest = MongoUtils.Hash(nonce + credentials.Username + passwordDigest);
167-
var authenticateCommand = new CommandDocument
168-
{
169-
{ "authenticate", 1 },
170-
{ "user", credentials.Username },
171-
{ "nonce", nonce },
172-
{ "key", digest }
173-
};
174-
175-
commandResult = RunCommand(databaseName, QueryFlags.None, authenticateCommand, false);
176-
if (!commandResult.Ok)
177-
{
178-
var message = string.Format("Invalid credentials for database '{0}'.", databaseName);
179-
throw new MongoAuthenticationException(
180-
message,
181-
new MongoCommandException(commandResult));
182-
}
183-
184-
var authentication = new Authentication(credentials);
185-
_authentications.Add(databaseName, authentication);
186-
}
187-
}
188-
189-
// check whether the connection can be used with the given database (and credentials)
190-
// the following are the only valid authentication states for a connection:
191-
// 1. the connection is not authenticated against any database
192-
// 2. the connection has a single authentication against the admin database (with a particular set of credentials)
193-
// 3. the connection has one or more authentications against any databases other than admin
194-
// (with the restriction that a particular database can only be authenticated against once and therefore with only one set of credentials)
195-
196-
// assume that IsAuthenticated was called first and returned false
197-
internal bool CanAuthenticate(string databaseName, MongoCredentials credentials)
198-
{
199-
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
200-
if (databaseName == null)
201-
{
202-
return true;
203-
}
204-
205-
if (_authentications.Count == 0)
206-
{
207-
// a connection with no existing authentications can authenticate anything
208-
return true;
209-
}
210-
else
211-
{
212-
// a connection with existing authentications can't be used without credentials
213-
if (credentials == null)
214-
{
215-
return false;
216-
}
217-
218-
// a connection with existing authentications can't be used with new admin credentials
219-
if (credentials.Admin)
220-
{
221-
return false;
222-
}
223-
224-
// a connection with an existing authentication to the admin database can't be used with any other credentials
225-
if (_authentications.ContainsKey("admin"))
226-
{
227-
return false;
228-
}
229-
230-
// a connection with an existing authentication to a database can't authenticate for the same database again
231-
if (_authentications.ContainsKey(databaseName))
232-
{
233-
return false;
234-
}
235-
236-
return true;
237-
}
238-
}
239-
240-
internal void CheckAuthentication(string databaseName, MongoCredentials credentials)
241-
{
242-
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
243-
if (credentials == null)
244-
{
245-
if (_authentications.Count != 0)
246-
{
247-
throw new InvalidOperationException("Connection requires credentials.");
248-
}
249-
}
250-
else
251-
{
252-
var authenticationDatabaseName = credentials.Admin ? "admin" : databaseName;
253-
Authentication authentication;
254-
if (_authentications.TryGetValue(authenticationDatabaseName, out authentication))
255-
{
256-
if (authentication.Credentials != credentials)
257-
{
258-
// this shouldn't happen because a connection would have been chosen from the connection pool only if it was viable
259-
if (authenticationDatabaseName == "admin")
260-
{
261-
throw new MongoInternalException("Connection already authenticated to the admin database with different credentials.");
262-
}
263-
else
264-
{
265-
throw new MongoInternalException("Connection already authenticated to the database with different credentials.");
266-
}
267-
}
268-
authentication.LastUsed = DateTime.UtcNow;
269-
}
270-
else
271-
{
272-
if (authenticationDatabaseName == "admin" && _authentications.Count != 0)
273-
{
274-
// this shouldn't happen because a connection would have been chosen from the connection pool only if it was viable
275-
throw new MongoInternalException("The connection cannot be authenticated against the admin database because it is already authenticated against other databases.");
276-
}
277-
Authenticate(authenticationDatabaseName, credentials);
278-
}
279-
}
280-
}
281-
282148
internal void Close()
283149
{
284150
lock (_connectionLock)
@@ -305,61 +171,13 @@ internal void Close()
305171
}
306172
}
307173

308-
internal bool IsAuthenticated(string databaseName, MongoCredentials credentials)
309-
{
310-
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
311-
if (databaseName == null)
312-
{
313-
return true;
314-
}
315-
316-
lock (_connectionLock)
317-
{
318-
if (credentials == null)
319-
{
320-
return _authentications.Count == 0;
321-
}
322-
else
323-
{
324-
var authenticationDatabaseName = credentials.Admin ? "admin" : databaseName;
325-
Authentication authentication;
326-
if (_authentications.TryGetValue(authenticationDatabaseName, out authentication))
327-
{
328-
return credentials == authentication.Credentials;
329-
}
330-
else
331-
{
332-
return false;
333-
}
334-
}
335-
}
336-
}
337-
338174
internal bool IsExpired()
339175
{
340176
var now = DateTime.UtcNow;
341177
return now > _createdAt + _serverInstance.Settings.MaxConnectionLifeTime
342178
|| now > _lastUsedAt + _serverInstance.Settings.MaxConnectionIdleTime;
343179
}
344180

345-
internal void Logout(string databaseName)
346-
{
347-
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
348-
lock (_connectionLock)
349-
{
350-
var logoutCommand = new CommandDocument("logout", 1);
351-
var commandResult = RunCommand(databaseName, QueryFlags.None, logoutCommand, false);
352-
if (!commandResult.Ok)
353-
{
354-
throw new MongoAuthenticationException(
355-
"Error logging off.",
356-
new MongoCommandException(commandResult));
357-
}
358-
359-
_authentications.Remove(databaseName);
360-
}
361-
}
362-
363181
internal void Open()
364182
{
365183
if (_state != MongoConnectionState.Initial)
@@ -405,6 +223,9 @@ internal void Open()
405223
_tcpClient = tcpClient;
406224
_stream = stream;
407225
_state = MongoConnectionState.Open;
226+
227+
new Authenticator(this, _serverInstance.Settings.CredentialsStore)
228+
.Authenticate();
408229
}
409230

410231
// this is a low level method that doesn't require a MongoServer
@@ -629,33 +450,5 @@ private HandleExceptionAction DetermineAction(Exception ex)
629450

630451
return HandleExceptionAction.CloseConnection; // this should always be the default action
631452
}
632-
633-
// private nested classes
634-
// keeps track of what credentials were used with a given database
635-
// and when that database was last used on this connection
636-
private class Authentication
637-
{
638-
// private fields
639-
private MongoCredentials _credentials;
640-
private DateTime _lastUsed;
641-
642-
// constructors
643-
public Authentication(MongoCredentials credentials)
644-
{
645-
_credentials = credentials;
646-
_lastUsed = DateTime.UtcNow;
647-
}
648-
649-
public MongoCredentials Credentials
650-
{
651-
get { return _credentials; }
652-
}
653-
654-
public DateTime LastUsed
655-
{
656-
get { return _lastUsed; }
657-
set { _lastUsed = value; }
658-
}
659-
}
660453
}
661454
}

MongoDB.Driver/Communication/MongoConnectionPool.cs

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System;
1717
using System.Collections.Generic;
1818
using System.Threading;
19+
using MongoDB.Driver.Communication;
1920

2021
namespace MongoDB.Driver.Internal
2122
{
@@ -86,12 +87,12 @@ public MongoServerInstance ServerInstance
8687
}
8788

8889
// internal methods
89-
internal MongoConnection AcquireConnection(string databaseName, MongoCredentials credentials)
90+
internal MongoConnection AcquireConnection()
9091
{
91-
return AcquireConnection(databaseName, credentials, _defaultAcquireConnectionOptions);
92+
return AcquireConnection(_defaultAcquireConnectionOptions);
9293
}
9394

94-
internal MongoConnection AcquireConnection(string databaseName, MongoCredentials credentials, AcquireConnectionOptions options)
95+
internal MongoConnection AcquireConnection(AcquireConnectionOptions options)
9596
{
9697
MongoConnection connectionToClose = null;
9798
try
@@ -112,39 +113,15 @@ internal MongoConnection AcquireConnection(string databaseName, MongoCredentials
112113
{
113114
if (_availableConnections.Count > 0)
114115
{
115-
// first try to find the most recently used connection that is already authenticated for this database
116-
for (int i = _availableConnections.Count - 1; i >= 0; i--)
116+
var connection = _availableConnections[_availableConnections.Count - 1];
117+
if (connection.IsExpired())
117118
{
118-
var connection = _availableConnections[i];
119-
if (connection.IsExpired())
120-
{
121-
_availableConnections.RemoveAt(i);
122-
connectionToClose = connection;
123-
return new MongoConnection(this);
124-
}
125-
else if (connection.IsAuthenticated(databaseName, credentials))
126-
{
127-
_availableConnections.RemoveAt(i);
128-
return connection;
129-
}
119+
connectionToClose = connection;
120+
connection = new MongoConnection(this);
130121
}
131122

132-
// otherwise find the most recently used connection that can be authenticated for this database
133-
for (int i = _availableConnections.Count - 1; i >= 0; i--)
134-
{
135-
var connection = _availableConnections[i];
136-
if (connection.CanAuthenticate(databaseName, credentials))
137-
{
138-
_availableConnections.RemoveAt(i);
139-
return connection;
140-
}
141-
}
142-
143-
// otherwise replace the least recently used connection with a brand new one
144-
// if this happens a lot the connection pool size should be increased
145-
connectionToClose = _availableConnections[0];
146-
_availableConnections.RemoveAt(0);
147-
return new MongoConnection(this);
123+
_availableConnections.RemoveAt(_availableConnections.Count - 1);
124+
return connection;
148125
}
149126

150127
// avoid waiting by creating a new connection if options allow it

0 commit comments

Comments
 (0)