diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/client/authenticate.cpp | 350 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 23 | ||||
-rw-r--r-- | src/mongo/client/authenticate_test.cpp | 111 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 24 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.cpp | 7 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.h | 12 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate_impl.cpp | 171 |
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) { |