/** * Copyright (C) 2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include #include "mongo/bson/util/bson_extract.h" #include "mongo/client/authenticate.h" #include "mongo/config.h" #include "mongo/db/jsobj.h" #include "mongo/unittest/unittest.h" #include "mongo/util/base64.h" #include "mongo/util/md5.hpp" #include "mongo/util/net/hostandport.h" #include "mongo/util/password_digest.h" namespace { using namespace mongo; using executor::RemoteCommandRequest; using executor::RemoteCommandResponse; using auth::RunCommandResultHandler; /** * Utility class to support tests in this file. Allows caller to load * with pre-made responses and requests to interject into authentication methods. */ class AuthClientTest : public mongo::unittest::Test { public: AuthClientTest() : _mockHost(), _millis(100), _username("PinkPanther"), _password("shhhhhhh"), _password_digest(createPasswordDigest(_username, _password)), _nonce("7ca422a24f326f2a"), _requests(), _responses() { _runCommandCallback = [this](RemoteCommandRequest request, RunCommandResultHandler handler) { runCommand(std::move(request), handler); }; // create our digest md5digest d; { 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.c_str(), _username.size()); md5_append(&st, (const md5_byte_t*)_password_digest.c_str(), _password_digest.size()); md5_finish(&st, d); } _digest = digestToString(d); } // protected: void runCommand(RemoteCommandRequest request, RunCommandResultHandler handler) { // Validate the received request ASSERT(!_requests.empty()); RemoteCommandRequest expected = _requests.front(); ASSERT(expected.dbname == request.dbname); ASSERT_BSONOBJ_EQ(expected.cmdObj, request.cmdObj); _requests.pop(); // Then pop a response and call the handler ASSERT(!_responses.empty()); handler(_responses.front()); _responses.pop(); } void reset() { // If there are things left then we did something wrong. ASSERT(_responses.empty()); ASSERT(_requests.empty()); } void pushResponse(const BSONObj& cmd) { _responses.emplace(cmd, BSONObj(), _millis); } void pushRequest(StringData dbname, const BSONObj& cmd) { _requests.emplace(_mockHost, dbname.toString(), cmd, nullptr); } 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. HostAndPort _mockHost; Milliseconds _millis; // Some credentials std::string _username; std::string _password; std::string _password_digest; std::string _digest; std::string _nonce; std::queue _requests; std::queue _responses; }; TEST_F(AuthClientTest, MongoCR) { 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()); }); } #ifdef MONGO_CONFIG_SSL 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