summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/client/authenticate.cpp350
-rw-r--r--src/mongo/client/authenticate.h23
-rw-r--r--src/mongo/client/authenticate_test.cpp111
-rw-r--r--src/mongo/client/dbclient.cpp24
-rw-r--r--src/mongo/client/sasl_client_authenticate.cpp7
-rw-r--r--src/mongo/client/sasl_client_authenticate.h12
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp171
7 files changed, 440 insertions, 258 deletions
diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp
index ce52b336356..a272386253d 100644
--- a/src/mongo/client/authenticate.cpp
+++ b/src/mongo/client/authenticate.cpp
@@ -48,11 +48,18 @@ namespace auth {
using executor::RemoteCommandRequest;
using executor::RemoteCommandResponse;
+using AuthRequest = StatusWith<RemoteCommandRequest>;
+
+const char* const kMechanismMongoCR = "MONGODB-CR";
+const char* const kMechanismMongoX509 = "MONGODB-X509";
+const char* const kMechanismSaslPlain = "PLAIN";
+const char* const kMechanismGSSAPI = "GSSAPI";
+const char* const kMechanismScramSha1 = "SCRAM-SHA-1";
+
namespace {
-// TODO: These constants need to be cleaned up.
-const char* const saslCommandUserSourceFieldName = "userSource";
-const BSONObj getnoncecmdobj = fromjson("{getnonce:1}");
+const char* const kUserSourceFieldName = "userSource";
+const BSONObj kGetNonceCmd = BSON("getnonce" << 1);
bool isOk(const BSONObj& o) {
return getStatusFromCommandResult(o).isOK();
@@ -65,39 +72,64 @@ BSONObj getFallbackAuthParams(const BSONObj& params) {
return params["fallbackParams"].Obj();
}
-// Use the MONGODB-CR protocol to authenticate as "username" against the database "dbname",
-// with the given password. If digestPassword is false, the password is assumed to be
-// pre-digested. Returns false on failure, and sets "errmsg".
-bool authMongoCR(RunCommandHook runCommand,
- StringData dbname,
- StringData username,
- StringData password_text,
- BSONObj* info,
- bool digestPassword) {
- auto request = RemoteCommandRequest();
- request.cmdObj = getnoncecmdobj;
- request.dbname = dbname.toString();
-
- std::string password = password_text.toString();
- if (digestPassword)
- password = createPasswordDigest(username, password_text);
-
- std::string nonce;
- invariant(info != nullptr && runCommand);
- auto runCommandHandler = [&info](StatusWith<RemoteCommandResponse> response) {
- *info = response.getValue().data.getOwned();
- };
-
- runCommand(request, runCommandHandler);
- if (!isOk(*info)) {
- return false;
+StatusWith<std::string> extractDBField(const BSONObj& params) {
+ std::string db;
+ if (params.hasField(kUserSourceFieldName)) {
+ if (!bsonExtractStringField(params, kUserSourceFieldName, &db).isOK()) {
+ return {ErrorCodes::AuthenticationFailed, "userSource field must contain a string"};
+ }
+ } else {
+ if (!bsonExtractStringField(params, saslCommandUserDBFieldName, &db).isOK()) {
+ return {ErrorCodes::AuthenticationFailed, "db field must contain a string"};
+ }
}
- {
- BSONElement e = info->getField("nonce");
- verify(e.type() == String);
- nonce = e.valuestr();
- }
+ return std::move(db);
+}
+
+//
+// MONGODB-CR
+//
+
+AuthRequest createMongoCRGetNonceCmd(const BSONObj& params) {
+ auto db = extractDBField(params);
+ if (!db.isOK())
+ return std::move(db.getStatus());
+
+ auto request = RemoteCommandRequest();
+ request.cmdObj = kGetNonceCmd;
+ request.dbname = db.getValue();
+
+ return std::move(request);
+}
+
+AuthRequest createMongoCRAuthenticateCmd(const BSONObj& params, StringData nonce) {
+ std::string username;
+ auto response = bsonExtractStringField(params, saslCommandUserFieldName, &username);
+ if (!response.isOK())
+ return response;
+
+ std::string password;
+ response = bsonExtractStringField(params, saslCommandPasswordFieldName, &password);
+ if (!response.isOK())
+ return response;
+
+ bool shouldDigest;
+ response = bsonExtractBooleanFieldWithDefault(
+ params, saslCommandDigestPasswordFieldName, true, &shouldDigest);
+ if (!response.isOK())
+ return response;
+
+ std::string digested = password;
+ if (shouldDigest)
+ digested = createPasswordDigest(username, password);
+
+ auto db = extractDBField(params);
+ if (!db.isOK())
+ return std::move(db.getStatus());
+
+ auto request = RemoteCommandRequest();
+ request.dbname = db.getValue();
BSONObjBuilder b;
{
@@ -106,124 +138,200 @@ bool authMongoCR(RunCommandHook runCommand,
{
md5_state_t st;
md5_init(&st);
- md5_append(&st, (const md5_byte_t*)nonce.c_str(), nonce.size());
- md5_append(&st, (const md5_byte_t*)username.rawData(), username.size());
- md5_append(&st, (const md5_byte_t*)password.c_str(), password.size());
+ md5_append(&st, reinterpret_cast<const md5_byte_t*>(nonce.rawData()), nonce.size());
+ md5_append(&st, reinterpret_cast<const md5_byte_t*>(username.c_str()), username.size());
+ md5_append(&st, reinterpret_cast<const md5_byte_t*>(digested.c_str()), digested.size());
md5_finish(&st, d);
}
b << "key" << digestToString(d);
- request.cmdObj = b.done();
+ request.cmdObj = b.obj();
}
+ return std::move(request);
+}
- runCommand(request, runCommandHandler);
- return isOk(*info);
+void authMongoCR(RunCommandHook runCommand, const BSONObj& params, AuthCompletionHandler handler) {
+ invariant(runCommand);
+ invariant(handler);
+
+ // Step 1: send getnonce command, receive nonce
+ auto nonceRequest = createMongoCRGetNonceCmd(params);
+ if (!nonceRequest.isOK())
+ return handler(std::move(nonceRequest.getStatus()));
+
+ runCommand(nonceRequest.getValue(),
+ [runCommand, params, handler](AuthResponse response) {
+ if (!response.isOK())
+ return handler(std::move(response));
+
+ // Ensure response was valid
+ std::string nonce;
+ BSONObj nonceResponse = response.getValue().data;
+ auto valid = bsonExtractStringField(nonceResponse, "nonce", &nonce);
+ if (!valid.isOK())
+ return handler({ErrorCodes::AuthenticationFailed,
+ "Invalid nonce response: " + nonceResponse.toString()});
+
+ // Step 2: send authenticate command, receive response
+ auto authRequest = createMongoCRAuthenticateCmd(params, nonce);
+ if (!authRequest.isOK())
+ return handler(std::move(authRequest.getStatus()));
+
+ runCommand(authRequest.getValue(), handler);
+ });
}
-// Use the MONGODB-X509 protocol to authenticate as "username." The certificate details
-// has already been communicated automatically as part of the connect call.
-// Returns false on failure and set "errmsg".
-bool authX509(RunCommandHook runCommand, StringData dbname, StringData username, BSONObj* info) {
- BSONObjBuilder cmdBuilder;
- cmdBuilder << "authenticate" << 1 << "mechanism"
- << "MONGODB-X509"
- << "user" << username;
+//
+// X-509
+//
+
+AuthRequest createX509AuthCmd(const BSONObj& params, StringData clientName) {
+ auto db = extractDBField(params);
+ if (!db.isOK())
+ return std::move(db.getStatus());
auto request = RemoteCommandRequest();
- request.dbname = dbname.toString();
- request.cmdObj = cmdBuilder.done();
+ request.dbname = db.getValue();
- runCommand(request,
- [&info](StatusWith<RemoteCommandResponse> response) {
- *info = response.getValue().data.getOwned();
- });
+ std::string username;
+ auto response = bsonExtractStringField(params, saslCommandUserFieldName, &username);
+ if (!response.isOK())
+ return response;
- return isOk(*info);
+ if (clientName.toString() == "") {
+ return {ErrorCodes::AuthenticationFailed,
+ "Please enable SSL on the client-side to use the MONGODB-X509 authentication "
+ "mechanism."};
+ }
+
+ if (username != clientName.toString()) {
+ StringBuilder message;
+ message << "Username \"";
+ message << params[saslCommandUserFieldName].valuestr();
+ message << "\" does not match the provided client certificate user \"";
+ message << clientName.toString() << "\"";
+ return {ErrorCodes::AuthenticationFailed, message.str()};
+ }
+
+ request.cmdObj = BSON("authenticate" << 1 << "mechanism"
+ << "MONGODB-X509"
+ << "user" << username);
+ return std::move(request);
+}
+
+// Use the MONGODB-X509 protocol to authenticate as "username." The certificate details
+// have already been communicated automatically as part of the connect call.
+void authX509(RunCommandHook runCommand,
+ const BSONObj& params,
+ StringData clientName,
+ AuthCompletionHandler handler) {
+ invariant(runCommand);
+ invariant(handler);
+
+ // Just 1 step: send authenticate command, receive response
+ auto authRequest = createX509AuthCmd(params, clientName);
+ if (!authRequest.isOK())
+ return handler(std::move(authRequest.getStatus()));
+
+ runCommand(authRequest.getValue(), handler);
}
+//
+// General Auth
+//
+
void auth(RunCommandHook runCommand,
const BSONObj& params,
StringData hostname,
- StringData clientName) {
+ StringData clientName,
+ AuthCompletionHandler handler) {
std::string mechanism;
+ auto response = bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism);
+ if (!response.isOK())
+ return handler(std::move(response));
+
+ if (mechanism != kMechanismMongoCR && mechanism != kMechanismMongoX509 &&
+ mechanism != kMechanismSaslPlain && mechanism != kMechanismGSSAPI &&
+ mechanism != kMechanismScramSha1) {
+ return handler(
+ {ErrorCodes::AuthenticationFailed, "Auth mechanism " + mechanism + " not supported."});
+ }
- uassertStatusOK(bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism));
+ if (params.hasField(saslCommandUserDBFieldName) && params.hasField(kUserSourceFieldName)) {
+ return handler({ErrorCodes::AuthenticationFailed,
+ "You cannot specify both 'db' and 'userSource'. Please use only 'db'."});
+ }
- uassert(17232,
- "You cannot specify both 'db' and 'userSource'. Please use only 'db'.",
- !(params.hasField(saslCommandUserDBFieldName) &&
- params.hasField(saslCommandUserSourceFieldName)));
+ if (mechanism == kMechanismMongoCR)
+ return authMongoCR(runCommand, params, handler);
- if (mechanism == StringData("MONGODB-CR", StringData::LiteralTag())) {
- std::string db;
- if (params.hasField(saslCommandUserSourceFieldName)) {
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db));
- } else {
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db));
- }
- std::string user;
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user));
- std::string password;
- uassertStatusOK(bsonExtractStringField(params, saslCommandPasswordFieldName, &password));
- bool digestPassword;
- uassertStatusOK(bsonExtractBooleanFieldWithDefault(
- params, saslCommandDigestPasswordFieldName, true, &digestPassword));
- BSONObj result;
- uassert(result["code"].Int(),
- result.toString(),
- authMongoCR(runCommand, db, user, password, &result, digestPassword));
- }
#ifdef MONGO_CONFIG_SSL
- else if (mechanism == StringData("MONGODB-X509", StringData::LiteralTag())) {
- std::string db;
- if (params.hasField(saslCommandUserSourceFieldName)) {
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db));
- } else {
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db));
- }
- std::string user;
- uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user));
-
- uassert(ErrorCodes::AuthenticationFailed,
- "Please enable SSL on the client-side to use the MONGODB-X509 "
- "authentication mechanism.",
- clientName.toString() != "");
+ else if (mechanism == kMechanismMongoX509)
+ return authX509(runCommand, params, clientName, handler);
+#endif
- uassert(ErrorCodes::AuthenticationFailed,
- "Username \"" + user + "\" does not match the provided client certificate user \"" +
- clientName.toString() + "\"",
- user == clientName.toString());
+ else if (saslClientAuthenticate != nullptr)
+ return saslClientAuthenticate(runCommand, hostname, params, handler);
- BSONObj result;
- uassert(result["code"].Int(), result.toString(), authX509(runCommand, db, user, &result));
- }
-#endif
- else if (saslClientAuthenticate != nullptr) {
- uassertStatusOK(saslClientAuthenticate(runCommand, hostname, params));
- } else {
- uasserted(ErrorCodes::BadValue,
- mechanism + " mechanism support not compiled into client library.");
- }
+ return handler({ErrorCodes::AuthenticationFailed,
+ mechanism + " mechanism support not compiled into client library."});
};
+bool needsFallback(const AuthResponse& response) {
+ // TODO: BadValue is sometimes returned for auth failures with unsupported mechanisms
+ // in 2.6 servers. We should investigate removing this BadValue check eventually.
+ return (response == ErrorCodes::BadValue || response == ErrorCodes::CommandNotFound);
+}
+
+void asyncAuth(RunCommandHook runCommand,
+ const BSONObj& params,
+ StringData hostname,
+ StringData clientName,
+ AuthCompletionHandler handler) {
+ auth(runCommand,
+ params,
+ hostname,
+ clientName,
+ [runCommand, params, hostname, clientName, handler](AuthResponse response) {
+ // If auth failed, try again with fallback params when appropriate
+ if (needsFallback(response))
+ return auth(runCommand,
+ std::move(getFallbackAuthParams(params)),
+ hostname,
+ clientName,
+ handler);
+
+ // otherwise, call handler
+ return handler(std::move(response));
+ });
+}
+
} // namespace
void authenticateClient(const BSONObj& params,
StringData hostname,
StringData clientName,
- RunCommandHook runCommand) {
- try {
- auth(runCommand, params, hostname, clientName);
- return;
- } catch (const UserException& ex) {
- if (getFallbackAuthParams(params).isEmpty() ||
- (ex.getCode() != ErrorCodes::BadValue && ex.getCode() != ErrorCodes::CommandNotFound)) {
- throw ex;
- }
+ RunCommandHook runCommand,
+ AuthCompletionHandler handler) {
+ if (handler) {
+ // Run asynchronously
+ return asyncAuth(std::move(runCommand), params, hostname, clientName, std::move(handler));
+ } else {
+ // Run synchronously through async framework
+ // NOTE: this assumes that runCommand executes synchronously.
+ asyncAuth(runCommand,
+ params,
+ hostname,
+ clientName,
+ [](AuthResponse response) {
+ // DBClient expects us to throw in case of an auth error.
+ uassertStatusOK(response);
+
+ auto serverResponse = response.getValue().data;
+ uassert(ErrorCodes::AuthenticationFailed,
+ serverResponse["errmsg"].str(),
+ isOk(serverResponse));
+ });
}
-
- // BadValue or CommandNotFound indicates unsupported auth mechanism so fall back to
- // MONGODB-CR for 2.6 compatibility.
- auth(runCommand, getFallbackAuthParams(params), hostname, clientName);
}
BSONObj buildAuthParams(StringData dbname,
diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h
index 7a5a40f6075..65c649c49c2 100644
--- a/src/mongo/client/authenticate.h
+++ b/src/mongo/client/authenticate.h
@@ -43,16 +43,28 @@ class BSONObj;
namespace auth {
-using RunCommandResultHandler = stdx::function<void(StatusWith<executor::RemoteCommandResponse>)>;
+using AuthResponse = StatusWith<executor::RemoteCommandResponse>;
+using AuthCompletionHandler = stdx::function<void(AuthResponse)>;
+using RunCommandResultHandler = AuthCompletionHandler;
using RunCommandHook =
stdx::function<void(executor::RemoteCommandRequest, RunCommandResultHandler)>;
/**
+ * Names for supported authentication mechanisms.
+ */
+
+extern const char* const kMechanismMongoCR;
+extern const char* const kMechanismMongoX509;
+extern const char* const kMechanismSaslPlain;
+extern const char* const kMechanismGSSAPI;
+extern const char* const kMechanismScramSha1;
+
+/**
* Authenticate a user.
*
* Pass the default hostname for this client in through "hostname." If SSL is enabled and
* there is a stored client subject name, pass that through the "clientSubjectName" parameter.
- * Otherwise, "clientSubjectName" will be ignored, pass in any string.
+ * Otherwise, "clientSubjectName" will be silently ignored, pass in any string.
*
* The "params" BSONObj should be initialized with some of the fields below. Which fields
* are required depends on the mechanism, which is mandatory.
@@ -71,6 +83,10 @@ using RunCommandHook =
* Other fields in "params" are silently ignored. A "params" object can be constructed
* using the buildAuthParams() method.
*
+ * If a "handler" is provided, this call will execute asynchronously and "handler" will be
+ * invoked when authentication has completed. If no handler is provided, authenticateClient
+ * will run synchronously.
+ *
* Returns normally on success, and throws on error. Throws a DBException with getCode() ==
* ErrorCodes::AuthenticationFailed if authentication is rejected. All other exceptions are
* tantamount to authentication failure, but may also indicate more serious problems.
@@ -78,7 +94,8 @@ using RunCommandHook =
void authenticateClient(const BSONObj& params,
StringData hostname,
StringData clientSubjectName,
- RunCommandHook runCommand);
+ RunCommandHook runCommand,
+ AuthCompletionHandler handler = AuthCompletionHandler());
/**
* Build a BSONObject representing parameters to be passed to authenticateClient(). Takes
diff --git a/src/mongo/client/authenticate_test.cpp b/src/mongo/client/authenticate_test.cpp
index 75a12c11e2d..0de2a1e9670 100644
--- a/src/mongo/client/authenticate_test.cpp
+++ b/src/mongo/client/authenticate_test.cpp
@@ -65,7 +65,7 @@ public:
_responses() {
_runCommandCallback =
[this](RemoteCommandRequest request, RunCommandResultHandler handler) {
- runCommand(request, handler);
+ runCommand(std::move(request), handler);
};
// create our digest
@@ -86,7 +86,6 @@ public:
// Validate the received request
ASSERT(!_requests.empty());
RemoteCommandRequest expected = _requests.front();
-
ASSERT(expected.dbname == request.dbname);
ASSERT_EQ(expected.cmdObj, request.cmdObj);
_requests.pop();
@@ -111,6 +110,50 @@ public:
_requests.emplace(_mockHost, dbname.toString(), cmd);
}
+ BSONObj loadMongoCRConversation() {
+ // 1. Client sends 'getnonce' command
+ pushRequest("admin", BSON("getnonce" << 1));
+
+ // 2. Client receives nonce
+ pushResponse(BSON("nonce" << _nonce << "ok" << 1));
+
+ // 3. Client sends 'authenticate' command
+ pushRequest("admin",
+ BSON("authenticate" << 1 << "nonce" << _nonce << "user" << _username << "key"
+ << _digest));
+
+ // 4. Client receives 'ok'
+ pushResponse(BSON("ok" << 1));
+
+ // Call clientAuthenticate()
+ return BSON("mechanism"
+ << "MONGODB-CR"
+ << "db"
+ << "admin"
+ << "user" << _username << "pwd" << _password << "digest"
+ << "true");
+ }
+
+
+ BSONObj loadX509Conversation() {
+ // 1. Client sends 'authenticate' command
+ pushRequest("$external",
+ BSON("authenticate" << 1 << "mechanism"
+ << "MONGODB-X509"
+ << "user" << _username));
+
+ // 2. Client receives 'ok'
+ pushResponse(BSON("ok" << 1));
+
+ // Call clientAuthenticate()
+ return BSON("mechanism"
+ << "MONGODB-X509"
+ << "db"
+ << "$external"
+ << "user" << _username);
+ }
+
+
auth::RunCommandHook _runCommandCallback;
// Auth code doesn't use HostAndPort information.
@@ -129,49 +172,33 @@ public:
};
TEST_F(AuthClientTest, MongoCR) {
- // 1. Client sends 'getnonce' command
- pushRequest("admin", BSON("getnonce" << 1));
-
- // 2. Client receives nonce
- pushResponse(BSON("nonce" << _nonce << "ok" << 1));
-
- // 3. Client sends 'authenticate' command
- pushRequest(
- "admin",
- BSON("authenticate" << 1 << "nonce" << _nonce << "user" << _username << "key" << _digest));
-
- // 4. Client receives 'ok'
- pushResponse(BSON("ok" << 1));
-
- // Call clientAuthenticate()
- auto params = BSON("mechanism"
- << "MONGODB-CR"
- << "db"
- << "admin"
- << "user" << _username << "pwd" << _password << "digest"
- << "true");
- auth::authenticateClient(params, "", "", _runCommandCallback);
+ auto params = loadMongoCRConversation();
+ auth::authenticateClient(std::move(params), "", "", _runCommandCallback);
+}
+
+TEST_F(AuthClientTest, asyncMongoCR) {
+ auto params = loadMongoCRConversation();
+ auth::authenticateClient(std::move(params),
+ "",
+ "",
+ _runCommandCallback,
+ [this](auth::AuthResponse response) { ASSERT(response.isOK()); });
}
-TEST_F(AuthClientTest, X509) {
#ifdef MONGO_CONFIG_SSL
- // 1. Client sends 'authenticate' command
- pushRequest("$external",
- BSON("authenticate" << 1 << "mechanism"
- << "MONGODB-X509"
- << "user" << _username));
-
- // 2. Client receives 'ok'
- pushResponse(BSON("ok" << 1));
-
- // Call clientAuthenticate()
- auto params = BSON("mechanism"
- << "MONGODB-X509"
- << "db"
- << "$external"
- << "user" << _username);
- auth::authenticateClient(params, "", _username, _runCommandCallback);
-#endif
+TEST_F(AuthClientTest, X509) {
+ auto params = loadX509Conversation();
+ auth::authenticateClient(std::move(params), "", _username, _runCommandCallback);
}
+TEST_F(AuthClientTest, asyncX509) {
+ auto params = loadX509Conversation();
+ auth::authenticateClient(std::move(params),
+ "",
+ _username,
+ _runCommandCallback,
+ [this](auth::AuthResponse response) { ASSERT(response.isOK()); });
+}
+#endif
+
} // namespace
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp
index f3d7fe3cc30..03bf154e9d9 100644
--- a/src/mongo/client/dbclient.cpp
+++ b/src/mongo/client/dbclient.cpp
@@ -533,21 +533,27 @@ void DBClientWithCommands::_auth(const BSONObj& params) {
params,
HostAndPort(getServerAddress()).host(),
clientName,
- [this](RemoteCommandRequest request, auth::RunCommandResultHandler handler) {
+ [this](RemoteCommandRequest request, auth::AuthCompletionHandler handler) {
BSONObj info;
auto start = Date_t::now();
auto commandName = request.cmdObj.firstElementFieldName();
- auto reply = runCommandWithMetadata(
- request.dbname, commandName, request.metadata, request.cmdObj);
- BSONObj data = reply->getCommandReply().getOwned();
- BSONObj metadata = reply->getMetadata().getOwned();
- Milliseconds millis(Date_t::now() - start);
+ try {
+ auto reply = runCommandWithMetadata(
+ request.dbname, commandName, request.metadata, request.cmdObj);
- // Hand control back to authenticateClient()
- handler(
- StatusWith<RemoteCommandResponse>(RemoteCommandResponse(data, metadata, millis)));
+ BSONObj data = reply->getCommandReply().getOwned();
+ BSONObj metadata = reply->getMetadata().getOwned();
+ Milliseconds millis(Date_t::now() - start);
+
+ // Hand control back to authenticateClient()
+ handler(StatusWith<RemoteCommandResponse>(
+ RemoteCommandResponse(data, metadata, millis)));
+
+ } catch (...) {
+ handler(exceptionToStatus());
+ }
});
}
diff --git a/src/mongo/client/sasl_client_authenticate.cpp b/src/mongo/client/sasl_client_authenticate.cpp
index 4222cc9a0f6..ecb7dfd7d1e 100644
--- a/src/mongo/client/sasl_client_authenticate.cpp
+++ b/src/mongo/client/sasl_client_authenticate.cpp
@@ -38,9 +38,10 @@ namespace mongo {
using namespace mongoutils;
-Status (*saslClientAuthenticate)(RunCommandHook runCommand,
- StringData hostname,
- const BSONObj& saslParameters) = NULL;
+void (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
+ StringData hostname,
+ const BSONObj& saslParameters,
+ auth::AuthCompletionHandler handler) = nullptr;
const char* const saslStartCommandName = "saslStart";
const char* const saslContinueCommandName = "saslContinue";
diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h
index f929c5660e2..8e52bee407d 100644
--- a/src/mongo/client/sasl_client_authenticate.h
+++ b/src/mongo/client/sasl_client_authenticate.h
@@ -29,16 +29,13 @@
#include "mongo/base/status.h"
#include "mongo/bson/bsontypes.h"
+#include "mongo/client/authenticate.h"
#include "mongo/executor/remote_command_request.h"
#include "mongo/executor/remote_command_response.h"
namespace mongo {
class BSONObj;
-using RunCommandResultHandler = stdx::function<void(StatusWith<executor::RemoteCommandResponse>)>;
-using RunCommandHook =
- stdx::function<void(executor::RemoteCommandRequest, RunCommandResultHandler)>;
-
/**
* Attempts to authenticate "client" using the SASL protocol.
*
@@ -70,9 +67,10 @@ using RunCommandHook =
* rejected. Other failures, all of which are tantamount to authentication failure, may also be
* returned.
*/
-extern Status (*saslClientAuthenticate)(RunCommandHook runCommand,
- StringData hostname,
- const BSONObj& saslParameters);
+extern void (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
+ StringData hostname,
+ const BSONObj& saslParameters,
+ auth::AuthCompletionHandler handler);
/**
* Extracts the payload field from "cmdObj", and store it into "*payload".
diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp
index 14f063e8972..a2dbc2f0888 100644
--- a/src/mongo/client/sasl_client_authenticate_impl.cpp
+++ b/src/mongo/client/sasl_client_authenticate_impl.cpp
@@ -116,7 +116,6 @@ Status extractPassword(const BSONObj& saslParameters,
* Returns Status::OK() on success.
*/
Status configureSession(SaslClientSession* session,
- RunCommandHook runCommand,
StringData hostname,
StringData targetDatabase,
const BSONObj& saslParameters) {
@@ -164,104 +163,130 @@ Status configureSession(SaslClientSession* session,
return session->initialize();
}
+void asyncSaslConversation(auth::RunCommandHook runCommand,
+ const std::shared_ptr<SaslClientSession>& session,
+ const BSONObj& saslCommandPrefix,
+ const BSONObj& inputObj,
+ StringData targetDatabase,
+ int saslLogLevel,
+ auth::AuthCompletionHandler handler) {
+ // Extract payload from previous step
+ std::string payload;
+ BSONType type;
+ auto status = saslExtractPayload(inputObj, &payload, &type);
+ if (!status.isOK())
+ return handler(std::move(status));
+
+ LOG(saslLogLevel) << "sasl client input: " << base64::encode(payload) << endl;
+
+ // Create new payload for our response
+ std::string responsePayload;
+ status = session->step(payload, &responsePayload);
+ if (!status.isOK())
+ return handler(std::move(status));
+
+ LOG(saslLogLevel) << "sasl client output: " << base64::encode(responsePayload) << endl;
+
+ // Build command using our new payload and conversationId
+ BSONObjBuilder commandBuilder;
+ commandBuilder.appendElements(saslCommandPrefix);
+ commandBuilder.appendBinData(saslCommandPayloadFieldName,
+ int(responsePayload.size()),
+ BinDataGeneral,
+ responsePayload.c_str());
+ BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
+ if (!conversationId.eoo())
+ commandBuilder.append(conversationId);
+
+ auto request = RemoteCommandRequest();
+ request.dbname = targetDatabase.toString();
+ request.cmdObj = commandBuilder.obj();
+
+ // Asynchronously continue the conversation
+ runCommand(
+ request,
+ [runCommand, session, targetDatabase, saslLogLevel, handler](auth::AuthResponse response) {
+ if (!response.isOK()) {
+ return handler(std::move(response));
+ }
+
+ auto serverResponse = response.getValue().data.getOwned();
+ auto code = getStatusFromCommandResult(serverResponse).code();
+
+ // Server versions 2.3.2 and earlier may return "ok: 1" with a non-zero
+ // "code" field, indicating a failure. Subsequent versions should
+ // return "ok: 0" on failure with a non-zero "code" field to indicate specific
+ // failure. In all versions, either (ok: 1, code: > 0) or (ok: 0, code optional)
+ // indicate failure.
+ if (code != ErrorCodes::OK) {
+ return handler({code, serverResponse[saslCommandErrmsgFieldName].str()});
+ }
+
+ // Exit if we have finished
+ if (session->isDone()) {
+ bool isServerDone = serverResponse[saslCommandDoneFieldName].trueValue();
+ if (!isServerDone) {
+ return handler({ErrorCodes::ProtocolError, "Client finished before server."});
+ }
+ return handler(std::move(response));
+ }
+
+ BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1);
+ asyncSaslConversation(runCommand,
+ session,
+ std::move(saslFollowupCommandPrefix),
+ std::move(serverResponse),
+ targetDatabase,
+ saslLogLevel,
+ handler);
+ });
+}
+
/**
* Driver for the client side of a sasl authentication session, conducted synchronously over
* "client".
*/
-Status saslClientAuthenticateImpl(RunCommandHook runCommand,
- StringData hostname,
- const BSONObj& saslParameters) {
+void saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
+ StringData hostname,
+ const BSONObj& saslParameters,
+ auth::AuthCompletionHandler handler) {
int saslLogLevel = getSaslClientLogLevel(saslParameters);
-
std::string targetDatabase;
try {
Status status = bsonExtractStringFieldWithDefault(
saslParameters, saslCommandUserDBFieldName, saslDefaultDBName, &targetDatabase);
if (!status.isOK())
- return status;
+ return handler(std::move(status));
} catch (const DBException& ex) {
- return ex.toStatus();
+ return handler(ex.toStatus());
}
std::string mechanism;
Status status =
bsonExtractStringField(saslParameters, saslCommandMechanismFieldName, &mechanism);
if (!status.isOK()) {
- return status;
+ return handler(std::move(status));
}
- std::unique_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
- status = configureSession(session.get(), runCommand, hostname, targetDatabase, saslParameters);
+ // NOTE: this must be a shared_ptr so that we can capture it in a lambda later on.
+ // Come C++14, we should be able to do this in a nicer way.
+ std::shared_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
+ status = configureSession(session.get(), hostname, targetDatabase, saslParameters);
if (!status.isOK())
- return status;
+ return handler(std::move(status));
BSONObj saslFirstCommandPrefix =
BSON(saslStartCommandName << 1 << saslCommandMechanismFieldName
<< session->getParameter(SaslClientSession::parameterMechanism));
-
- BSONObj saslFollowupCommandPrefix = BSON(saslContinueCommandName << 1);
- BSONObj saslCommandPrefix = saslFirstCommandPrefix;
BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
- bool isServerDone = false;
- while (!session->isDone()) {
- std::string payload;
- BSONType type;
-
- status = saslExtractPayload(inputObj, &payload, &type);
- if (!status.isOK())
- return status;
-
- LOG(saslLogLevel) << "sasl client input: " << base64::encode(payload) << endl;
-
- std::string responsePayload;
- status = session->step(payload, &responsePayload);
- if (!status.isOK())
- return status;
-
- LOG(saslLogLevel) << "sasl client output: " << base64::encode(responsePayload) << endl;
-
- BSONObjBuilder commandBuilder;
- commandBuilder.appendElements(saslCommandPrefix);
- commandBuilder.appendBinData(saslCommandPayloadFieldName,
- int(responsePayload.size()),
- BinDataGeneral,
- responsePayload.c_str());
- BSONElement conversationId = inputObj[saslCommandConversationIdFieldName];
- if (!conversationId.eoo())
- commandBuilder.append(conversationId);
-
- // Server versions 2.3.2 and earlier may return "ok: 1" with a non-zero "code" field,
- // indicating a failure. Subsequent versions should return "ok: 0" on failure with a
- // non-zero "code" field to indicate specific failure. In all versions, ok: 1, code: >0
- // and ok: 0, code optional, indicate failure.
- auto request = RemoteCommandRequest();
- request.dbname = targetDatabase;
- request.cmdObj = commandBuilder.obj();
-
- runCommand(request,
- [&inputObj](StatusWith<RemoteCommandResponse> response) {
- inputObj = response.getValue().data.getOwned();
- });
- bool ok = getStatusFromCommandResult(inputObj).isOK();
-
- ErrorCodes::Error code =
- ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt());
-
- if (!ok || code != ErrorCodes::OK) {
- if (code == ErrorCodes::OK)
- code = ErrorCodes::UnknownError;
-
- return Status(code, inputObj[saslCommandErrmsgFieldName].str());
- }
-
- isServerDone = inputObj[saslCommandDoneFieldName].trueValue();
- saslCommandPrefix = saslFollowupCommandPrefix;
- }
-
- if (!isServerDone)
- return Status(ErrorCodes::ProtocolError, "Client finished before server.");
- return Status::OK();
+ asyncSaslConversation(runCommand,
+ session,
+ std::move(saslFirstCommandPrefix),
+ std::move(inputObj),
+ targetDatabase,
+ saslLogLevel,
+ handler);
}
MONGO_INITIALIZER(SaslClientAuthenticateFunction)(InitializerContext* context) {