Skip to content

Commit

Permalink
CSHARP-603: implemented Sasl authentication support as well as suppor…
Browse files Browse the repository at this point in the history
…t for new delegated authentication support in server 2.4.
  • Loading branch information
craiggwilson committed Jan 17, 2013
1 parent e0f958e commit 401a45f
Show file tree
Hide file tree
Showing 76 changed files with 5,206 additions and 1,888 deletions.
219 changes: 6 additions & 213 deletions MongoDB.Driver/Communication/MongoConnection.cs
Expand Up @@ -14,14 +14,15 @@
*/ */


using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.IO; using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Driver.Communication;
using MongoDB.Driver.Communication.Security;


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


// constructors // constructors
internal MongoConnection(MongoConnectionPool connectionPool) internal MongoConnection(MongoConnectionPool connectionPool)
: this(connectionPool.ServerInstance)
{ {
_serverInstance = connectionPool.ServerInstance;
_connectionPool = connectionPool; _connectionPool = connectionPool;
_generationId = connectionPool.GenerationId; _generationId = connectionPool.GenerationId;
_createdAt = DateTime.UtcNow;
_state = MongoConnectionState.Initial;
} }


internal MongoConnection(MongoServerInstance serverInstance) internal MongoConnection(MongoServerInstance serverInstance)
Expand Down Expand Up @@ -147,138 +145,6 @@ public MongoConnectionState State
} }


// internal methods // internal methods
internal void Authenticate(string databaseName, MongoCredentials credentials)
{
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
lock (_connectionLock)
{
var nonceCommand = new CommandDocument("getnonce", 1);
var commandResult = RunCommand(databaseName, QueryFlags.None, nonceCommand, false);
if (!commandResult.Ok)
{
throw new MongoAuthenticationException(
"Error getting nonce for authentication.",
new MongoCommandException(commandResult));
}

var nonce = commandResult.Response["nonce"].AsString;
var passwordDigest = MongoUtils.Hash(credentials.Username + ":mongo:" + credentials.Password);
var digest = MongoUtils.Hash(nonce + credentials.Username + passwordDigest);
var authenticateCommand = new CommandDocument
{
{ "authenticate", 1 },
{ "user", credentials.Username },
{ "nonce", nonce },
{ "key", digest }
};

commandResult = RunCommand(databaseName, QueryFlags.None, authenticateCommand, false);
if (!commandResult.Ok)
{
var message = string.Format("Invalid credentials for database '{0}'.", databaseName);
throw new MongoAuthenticationException(
message,
new MongoCommandException(commandResult));
}

var authentication = new Authentication(credentials);
_authentications.Add(databaseName, authentication);
}
}

// check whether the connection can be used with the given database (and credentials)
// the following are the only valid authentication states for a connection:
// 1. the connection is not authenticated against any database
// 2. the connection has a single authentication against the admin database (with a particular set of credentials)
// 3. the connection has one or more authentications against any databases other than admin
// (with the restriction that a particular database can only be authenticated against once and therefore with only one set of credentials)

// assume that IsAuthenticated was called first and returned false
internal bool CanAuthenticate(string databaseName, MongoCredentials credentials)
{
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
if (databaseName == null)
{
return true;
}

if (_authentications.Count == 0)
{
// a connection with no existing authentications can authenticate anything
return true;
}
else
{
// a connection with existing authentications can't be used without credentials
if (credentials == null)
{
return false;
}

// a connection with existing authentications can't be used with new admin credentials
if (credentials.Admin)
{
return false;
}

// a connection with an existing authentication to the admin database can't be used with any other credentials
if (_authentications.ContainsKey("admin"))
{
return false;
}

// a connection with an existing authentication to a database can't authenticate for the same database again
if (_authentications.ContainsKey(databaseName))
{
return false;
}

return true;
}
}

internal void CheckAuthentication(string databaseName, MongoCredentials credentials)
{
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
if (credentials == null)
{
if (_authentications.Count != 0)
{
throw new InvalidOperationException("Connection requires credentials.");
}
}
else
{
var authenticationDatabaseName = credentials.Admin ? "admin" : databaseName;
Authentication authentication;
if (_authentications.TryGetValue(authenticationDatabaseName, out authentication))
{
if (authentication.Credentials != credentials)
{
// this shouldn't happen because a connection would have been chosen from the connection pool only if it was viable
if (authenticationDatabaseName == "admin")
{
throw new MongoInternalException("Connection already authenticated to the admin database with different credentials.");
}
else
{
throw new MongoInternalException("Connection already authenticated to the database with different credentials.");
}
}
authentication.LastUsed = DateTime.UtcNow;
}
else
{
if (authenticationDatabaseName == "admin" && _authentications.Count != 0)
{
// this shouldn't happen because a connection would have been chosen from the connection pool only if it was viable
throw new MongoInternalException("The connection cannot be authenticated against the admin database because it is already authenticated against other databases.");
}
Authenticate(authenticationDatabaseName, credentials);
}
}
}

internal void Close() internal void Close()
{ {
lock (_connectionLock) lock (_connectionLock)
Expand All @@ -305,61 +171,13 @@ internal void Close()
} }
} }


internal bool IsAuthenticated(string databaseName, MongoCredentials credentials)
{
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
if (databaseName == null)
{
return true;
}

lock (_connectionLock)
{
if (credentials == null)
{
return _authentications.Count == 0;
}
else
{
var authenticationDatabaseName = credentials.Admin ? "admin" : databaseName;
Authentication authentication;
if (_authentications.TryGetValue(authenticationDatabaseName, out authentication))
{
return credentials == authentication.Credentials;
}
else
{
return false;
}
}
}
}

internal bool IsExpired() internal bool IsExpired()
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
return now > _createdAt + _serverInstance.Settings.MaxConnectionLifeTime return now > _createdAt + _serverInstance.Settings.MaxConnectionLifeTime
|| now > _lastUsedAt + _serverInstance.Settings.MaxConnectionIdleTime; || now > _lastUsedAt + _serverInstance.Settings.MaxConnectionIdleTime;
} }


internal void Logout(string databaseName)
{
if (_state == MongoConnectionState.Closed) { throw new InvalidOperationException("Connection is closed."); }
lock (_connectionLock)
{
var logoutCommand = new CommandDocument("logout", 1);
var commandResult = RunCommand(databaseName, QueryFlags.None, logoutCommand, false);
if (!commandResult.Ok)
{
throw new MongoAuthenticationException(
"Error logging off.",
new MongoCommandException(commandResult));
}

_authentications.Remove(databaseName);
}
}

internal void Open() internal void Open()
{ {
if (_state != MongoConnectionState.Initial) if (_state != MongoConnectionState.Initial)
Expand Down Expand Up @@ -405,6 +223,9 @@ internal void Open()
_tcpClient = tcpClient; _tcpClient = tcpClient;
_stream = stream; _stream = stream;
_state = MongoConnectionState.Open; _state = MongoConnectionState.Open;

new Authenticator(this, _serverInstance.Settings.CredentialsStore)
.Authenticate();
} }


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


return HandleExceptionAction.CloseConnection; // this should always be the default action return HandleExceptionAction.CloseConnection; // this should always be the default action
} }

// private nested classes
// keeps track of what credentials were used with a given database
// and when that database was last used on this connection
private class Authentication
{
// private fields
private MongoCredentials _credentials;
private DateTime _lastUsed;

// constructors
public Authentication(MongoCredentials credentials)
{
_credentials = credentials;
_lastUsed = DateTime.UtcNow;
}

public MongoCredentials Credentials
{
get { return _credentials; }
}

public DateTime LastUsed
{
get { return _lastUsed; }
set { _lastUsed = value; }
}
}
} }
} }
43 changes: 10 additions & 33 deletions MongoDB.Driver/Communication/MongoConnectionPool.cs
Expand Up @@ -16,6 +16,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using MongoDB.Driver.Communication;


namespace MongoDB.Driver.Internal namespace MongoDB.Driver.Internal
{ {
Expand Down Expand Up @@ -86,12 +87,12 @@ public MongoServerInstance ServerInstance
} }


// internal methods // internal methods
internal MongoConnection AcquireConnection(string databaseName, MongoCredentials credentials) internal MongoConnection AcquireConnection()
{ {
return AcquireConnection(databaseName, credentials, _defaultAcquireConnectionOptions); return AcquireConnection(_defaultAcquireConnectionOptions);
} }


internal MongoConnection AcquireConnection(string databaseName, MongoCredentials credentials, AcquireConnectionOptions options) internal MongoConnection AcquireConnection(AcquireConnectionOptions options)
{ {
MongoConnection connectionToClose = null; MongoConnection connectionToClose = null;
try try
Expand All @@ -112,39 +113,15 @@ internal MongoConnection AcquireConnection(string databaseName, MongoCredentials
{ {
if (_availableConnections.Count > 0) if (_availableConnections.Count > 0)
{ {
// first try to find the most recently used connection that is already authenticated for this database var connection = _availableConnections[_availableConnections.Count - 1];
for (int i = _availableConnections.Count - 1; i >= 0; i--) if (connection.IsExpired())
{ {
var connection = _availableConnections[i]; connectionToClose = connection;
if (connection.IsExpired()) connection = new MongoConnection(this);
{
_availableConnections.RemoveAt(i);
connectionToClose = connection;
return new MongoConnection(this);
}
else if (connection.IsAuthenticated(databaseName, credentials))
{
_availableConnections.RemoveAt(i);
return connection;
}
} }


// otherwise find the most recently used connection that can be authenticated for this database _availableConnections.RemoveAt(_availableConnections.Count - 1);
for (int i = _availableConnections.Count - 1; i >= 0; i--) return connection;
{
var connection = _availableConnections[i];
if (connection.CanAuthenticate(databaseName, credentials))
{
_availableConnections.RemoveAt(i);
return connection;
}
}

// otherwise replace the least recently used connection with a brand new one
// if this happens a lot the connection pool size should be increased
connectionToClose = _availableConnections[0];
_availableConnections.RemoveAt(0);
return new MongoConnection(this);
} }


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

0 comments on commit 401a45f

Please sign in to comment.