diff options
author | Tyler Kaye <tyler.kaye@mongodb.com> | 2018-10-29 13:06:49 -0400 |
---|---|---|
committer | Tyler Kaye <tyler.kaye@mongodb.com> | 2018-11-14 13:30:44 -0500 |
commit | ddcf9f0572755a456632d036744276a09baf5760 (patch) | |
tree | d417e74a1fa34b835916422aaf1ddcdf7879d58c /src/mongo | |
parent | 106eba1584b61497c133896b5dab7a3cea49296d (diff) | |
download | mongo-ddcf9f0572755a456632d036744276a09baf5760.tar.gz |
SERVER-35212: Refactor shell code to enable default authentication database as admin
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/client/mongo_uri.cpp | 54 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri.h | 45 | ||||
-rw-r--r-- | src/mongo/client/replica_set_monitor.cpp | 2 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/mongo.cpp | 5 | ||||
-rw-r--r-- | src/mongo/shell/dbshell.cpp | 149 | ||||
-rw-r--r-- | src/mongo/shell/mongo.js | 8 | ||||
-rw-r--r-- | src/mongo/shell/shell_options.cpp | 1 | ||||
-rw-r--r-- | src/mongo/shell/shell_options.h | 1 |
8 files changed, 169 insertions, 96 deletions
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp index 597933712df..c8b76e776a8 100644 --- a/src/mongo/client/mongo_uri.cpp +++ b/src/mongo/client/mongo_uri.cpp @@ -40,6 +40,7 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/find_iterator.hpp> #include <boost/algorithm/string/predicate.hpp> +#include <boost/range/algorithm/count.hpp> #include "mongo/base/status_with.h" #include "mongo/bson/bsonobjbuilder.h" @@ -56,8 +57,6 @@ using namespace std::literals::string_literals; namespace { constexpr std::array<char, 16> hexits{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; -const mongo::StringData kURIPrefix{"mongodb://"}; -const mongo::StringData kURISRVPrefix{"mongodb+srv://"}; // This vector must remain sorted. It is over pairs to facilitate a call to `std::includes` using // a `std::map<std::string, std::string>` as the other parameter. @@ -112,6 +111,10 @@ namespace mongo { namespace { +constexpr StringData kURIPrefix = "mongodb://"_sd; +constexpr StringData kURISRVPrefix = "mongodb+srv://"_sd; +constexpr StringData kDefaultMongoHost = "127.0.0.1:27017"_sd; + /** * Helper Method for MongoURI::parse() to split a string into exactly 2 pieces by a char * delimiter. @@ -519,8 +522,53 @@ const boost::optional<std::string> MongoURI::getAppName() const { const auto optIter = _options.find("appName"); if (optIter != end(_options)) { return optIter->second; + } + return boost::none; +} + +std::string MongoURI::canonicalizeURIAsString() const { + StringBuilder uri; + uri << kURIPrefix; + if (!_user.empty()) { + uri << uriEncode(_user); + if (!_password.empty()) { + uri << ":" << uriEncode(_password); + } + uri << "@"; + } + + const auto& servers = _connectString.getServers(); + if (!servers.empty()) { + auto delimeter = ""; + for (auto& hostAndPort : servers) { + if (boost::count(hostAndPort.host(), ':') > 1) { + uri << delimeter << "[" << uriEncode(hostAndPort.host()) << "]" + << ":" << uriEncode(std::to_string(hostAndPort.port())); + } else if (StringData(hostAndPort.host()).endsWith(".sock")) { + uri << delimeter << uriEncode(hostAndPort.host()); + } else { + uri << delimeter << uriEncode(hostAndPort.host()) << ":" + << uriEncode(std::to_string(hostAndPort.port())); + } + delimeter = ","; + } } else { - return boost::none; + uri << kDefaultMongoHost; + } + + uri << "/"; + if (!_database.empty()) { + uri << uriEncode(_database); + } + + if (!_options.empty()) { + auto delimeter = ""; + uri << "?"; + for (const auto& pair : _options) { + uri << delimeter << uriEncode(pair.first) << "=" << uriEncode(pair.second); + delimeter = "&"; + } } + return uri.str(); } } // namespace mongo diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h index 9b61dcaebd9..c121fa21801 100644 --- a/src/mongo/client/mongo_uri.h +++ b/src/mongo/client/mongo_uri.h @@ -130,22 +130,64 @@ public: return _user; } + void setUser(std::string newUsername) { + _user = std::move(newUsername); + } + const std::string& getPassword() const { return _password; } + void setPassword(std::string newPassword) { + _password = std::move(newPassword); + } + const OptionsMap& getOptions() const { return _options; } + void addOption(std::string newKey, std::string newValue) { + _options[std::move(newKey)] = std::move(newValue); + } + + void setOptionIfNecessary(std::string uriParamKey, std::string value) { + const auto key = _options.find(uriParamKey); + if (key == end(_options) && !value.empty()) { + addOption(uriParamKey, value); + } + } + + boost::optional<std::string> getOption(const std::string& key) const { + const auto optIter = _options.find(key); + if (optIter != end(_options)) { + return optIter->second; + } + return boost::none; + } + const std::string& getDatabase() const { return _database; } + std::string getAuthenticationDatabase() { + auto authDB = _options.find("authSource"); + if (authDB != _options.end()) { + return authDB->second; + } else if (!_database.empty()) { + return _database; + } else { + return "admin"; + } + } + bool isValid() const { return _connectString.isValid(); } + const ConnectionString& connectionString() const { + return _connectString; + } + const std::string& toString() const { return _connectString.toString(); } @@ -161,6 +203,9 @@ public: const boost::optional<std::string> getAppName() const; + std::string canonicalizeURIAsString() const; + + boost::optional<bool> getRetryWrites() const { return _retryWrites; } diff --git a/src/mongo/client/replica_set_monitor.cpp b/src/mongo/client/replica_set_monitor.cpp index 769358dc9b0..0488b9100ec 100644 --- a/src/mongo/client/replica_set_monitor.cpp +++ b/src/mongo/client/replica_set_monitor.cpp @@ -854,6 +854,8 @@ HostAndPort Refresher::_refreshUntilMatches(const ReadPreferenceSetting* criteri if (_set->setUri.isValid()) { targetURI = _set->setUri.cloneURIForServer(ns.host); + targetURI.setUser(""); + targetURI.setPassword(""); } else { targetURI = MongoURI(ConnectionString(ns.host)); } diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index 08c0375e384..92d60d4eee0 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -715,8 +715,7 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { host = ValueWriter(cx, args.get(0)).toString(); } - auto statusWithHost = MongoURI::parse(host); - auto cs = uassertStatusOK(statusWithHost); + auto cs = uassertStatusOK(MongoURI::parse(host)); boost::optional<std::string> appname = cs.getAppName(); std::string errmsg; @@ -735,7 +734,7 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) { JS_SetPrivate(thisv, scope->trackedNew<std::shared_ptr<DBClientBase>>(conn.release())); o.setBoolean(InternedString::slaveOk, false); - o.setString(InternedString::host, cs.toString()); + o.setString(InternedString::host, cs.connectionString().toString()); auto defaultDB = cs.getDatabase() == "" ? "test" : cs.getDatabase(); o.setString(InternedString::defaultDB, defaultDB); diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp index e53e09d6ed5..3347c2fa0b1 100644 --- a/src/mongo/shell/dbshell.cpp +++ b/src/mongo/shell/dbshell.cpp @@ -92,7 +92,9 @@ bool inMultiLine = false; static AtomicBool atPrompt(false); // can eval before getting to prompt namespace { -const auto kDefaultMongoURL = "mongodb://127.0.0.1:27017"_sd; +const std::string kDefaultMongoHost = "127.0.0.1"s; +const std::string kDefaultMongoPort = "27017"s; +const std::string kDefaultMongoURL = "mongodb://"s + kDefaultMongoHost + ":"s + kDefaultMongoPort; // Initialize the featureCompatibilityVersion server parameter since the mongo shell does not have a // featureCompatibilityVersion document from which to initialize the parameter. The parameter is set @@ -239,7 +241,7 @@ void setupSignals() { string getURIFromArgs(const std::string& arg, const std::string& host, const std::string& port) { if (host.empty() && arg.empty() && port.empty()) { // Nothing provided, just play the default. - return kDefaultMongoURL.toString(); + return kDefaultMongoURL; } if ((str::startsWith(arg, "mongodb://") || str::startsWith(arg, "mongodb+srv://")) && @@ -247,7 +249,7 @@ string getURIFromArgs(const std::string& arg, const std::string& host, const std // mongo mongodb://blah return arg; } - if ((str::startsWith(host, "mongodb://") || str::startsWith(arg, "mongodb+srv://")) && + if ((str::startsWith(host, "mongodb://") || str::startsWith(host, "mongodb+srv://")) && arg.empty() && port.empty()) { // mongo --host mongodb://blah return host; @@ -726,16 +728,17 @@ static void edit(const string& whatToEdit) { } namespace { -bool mechanismRequiresPassword() { - using std::begin; - using std::end; - const std::string passwordlessMechanisms[] = {"GSSAPI", "MONGODB-X509"}; - auto isInShellParameters = [](const auto& mech) { - return mech == shellGlobalParams.authenticationMechanism; - }; - - return std::none_of( - begin(passwordlessMechanisms), end(passwordlessMechanisms), isInShellParameters); +bool mechanismRequiresPassword(const MongoURI& uri) { + if (const auto authMechanisms = uri.getOption("authMechanism")) { + constexpr std::array<StringData, 2> passwordlessMechanisms{"GSSAPI"_sd, "MONGODB-X509"_sd}; + const std::string& authMechanism = authMechanisms.get(); + for (const auto& mechanism : passwordlessMechanisms) { + if (mechanism.toString() == authMechanism) { + return false; + } + } + } + return true; } } // namespace @@ -780,30 +783,42 @@ int _main(int argc, char* argv[], char** envp) { ->attachAppender(std::make_unique<logger::ConsoleAppender<logger::MessageEventEphemeral>>( std::make_unique<logger::MessageEventUnadornedEncoder>())); + // Get the URL passed to the shell std::string& cmdlineURI = shellGlobalParams.url; + + // Parse the output of getURIFromArgs which will determine if --host passed in a URI MongoURI parsedURI; - if (!cmdlineURI.empty()) { - parsedURI = uassertStatusOK(MongoURI::parse(stdx::as_const(cmdlineURI))); - } + parsedURI = uassertStatusOK(MongoURI::parse(getURIFromArgs( + cmdlineURI, escape(shellGlobalParams.dbhost), escape(shellGlobalParams.port)))); - // We create an altered URI from the one passed so that we can pass that to replica set - // monitors. This is to avoid making potentially breaking changes to the replica set monitor - // code. - std::string processedURI = cmdlineURI; - auto pos = cmdlineURI.find('@'); - auto protocolLength = processedURI.find("://"); - if (pos != std::string::npos && protocolLength != std::string::npos) { - processedURI = - processedURI.substr(0, protocolLength) + "://" + processedURI.substr(pos + 1); - } + // TODO: add in all of the relevant shellGlobalParams to parsedURI + parsedURI.setOptionIfNecessary("compressors"s, shellGlobalParams.networkMessageCompressors); + parsedURI.setOptionIfNecessary("authMechanism"s, shellGlobalParams.authenticationMechanism); + parsedURI.setOptionIfNecessary("authSource"s, shellGlobalParams.authenticationDatabase); + parsedURI.setOptionIfNecessary("gssapiServiceName"s, shellGlobalParams.gssapiServiceName); + parsedURI.setOptionIfNecessary("gssapiHostName"s, shellGlobalParams.gssapiHostName); + bool usingPassword = !shellGlobalParams.password.empty(); if (!shellGlobalParams.nodb) { // connect to db + if (mechanismRequiresPassword(parsedURI) && + (parsedURI.getUser().size() || shellGlobalParams.username.size())) { + usingPassword = true; + } + if (usingPassword && parsedURI.getPassword().empty()) { + if (!shellGlobalParams.password.empty()) { + parsedURI.setPassword(stdx::as_const(shellGlobalParams.password)); + } else { + parsedURI.setPassword(mongo::askPassword()); + } + } + if (parsedURI.getUser().empty() && !shellGlobalParams.username.empty()) { + parsedURI.setUser(stdx::as_const(shellGlobalParams.username)); + } + stringstream ss; if (mongo::serverGlobalParams.quiet.load()) ss << "__quiet = true;"; - ss << "db = connect( \"" - << getURIFromArgs(processedURI, shellGlobalParams.dbhost, shellGlobalParams.port) - << "\");"; + ss << "db = connect( \"" << parsedURI.canonicalizeURIAsString() << "\");"; if (shellGlobalParams.shouldRetryWrites || parsedURI.getRetryWrites()) { // If the --retryWrites cmdline argument or retryWrites URI param was specified, then @@ -813,46 +828,8 @@ int _main(int argc, char* argv[], char** envp) { } mongo::shell_utils::_dbConnect = ss.str(); - - if (cmdlineURI.size()) { - const auto compressionKey = parsedURI.getOptions().find("compressors"); - if (compressionKey != end(parsedURI.getOptions()) && - shellGlobalParams.networkMessageCompressors.empty()) { - shellGlobalParams.networkMessageCompressors = compressionKey->second; - } - const auto mechanismKey = parsedURI.getOptions().find("authMechanism"); - if (mechanismKey != end(parsedURI.getOptions()) && - shellGlobalParams.authenticationMechanism.empty()) { - shellGlobalParams.authenticationMechanism = mechanismKey->second; - } - - if (mechanismRequiresPassword() && - (parsedURI.getUser().size() || shellGlobalParams.username.size())) { - shellGlobalParams.usingPassword = true; - } - if (shellGlobalParams.usingPassword && shellGlobalParams.password.empty()) { - shellGlobalParams.password = - parsedURI.getPassword().size() ? parsedURI.getPassword() : mongo::askPassword(); - } - if (parsedURI.getUser().size() && shellGlobalParams.username.empty()) { - shellGlobalParams.username = parsedURI.getUser(); - } - auto authParam = parsedURI.getOptions().find(kAuthParam); - if (authParam != end(parsedURI.getOptions()) && - shellGlobalParams.authenticationDatabase.empty()) { - shellGlobalParams.authenticationDatabase = authParam->second; - } - } else if (shellGlobalParams.usingPassword && shellGlobalParams.password.empty()) { - shellGlobalParams.password = mongo::askPassword(); - } } - // We now substitute the altered URI to permit the replica set monitors to see it without - // usernames. This is to avoid making potentially breaking changes to the replica set monitor - // code. - cmdlineURI = processedURI; - - // Construct the authentication-related code to execute on shell startup. // // This constructs and immediately executes an anonymous function, to avoid @@ -865,41 +842,39 @@ int _main(int argc, char* argv[], char** envp) { // }()) stringstream authStringStream; authStringStream << "(function() { " << endl; - if (!shellGlobalParams.authenticationMechanism.empty()) { + + + if (const auto authMechanisms = parsedURI.getOption("authMechanism")) { authStringStream << "DB.prototype._defaultAuthenticationMechanism = \"" - << escape(shellGlobalParams.authenticationMechanism) << "\";" << endl; + << escape(authMechanisms.get()) << "\";" << endl; } - if (!shellGlobalParams.gssapiServiceName.empty()) { + if (const auto gssapiServiveName = parsedURI.getOption("gssapiServiceName")) { authStringStream << "DB.prototype._defaultGssapiServiceName = \"" - << escape(shellGlobalParams.gssapiServiceName) << "\";" << endl; + << escape(gssapiServiveName.get()) << "\";" << endl; } - if (!shellGlobalParams.nodb && (!shellGlobalParams.username.empty() || - shellGlobalParams.authenticationMechanism == "MONGODB-X509")) { - authStringStream << "var username = \"" << escape(shellGlobalParams.username) << "\";" - << endl; - if (shellGlobalParams.usingPassword) { - authStringStream << "var password = \"" << escape(shellGlobalParams.password) << "\";" + if (!shellGlobalParams.nodb && + (!parsedURI.getUser().empty() || + parsedURI.getOption("authMechanism").get_value_or("") == "MONGODB-X509")) { + authStringStream << "var username = \"" << escape(parsedURI.getUser()) << "\";" << endl; + if (usingPassword) { + authStringStream << "var password = \"" << escape(parsedURI.getPassword()) << "\";" << endl; } - if (shellGlobalParams.authenticationDatabase.empty()) { - authStringStream << "var authDb = db;" << endl; - } else { - authStringStream << "var authDb = db.getSiblingDB(\"" - << escape(shellGlobalParams.authenticationDatabase) << "\");" << endl; - } + authStringStream << "var authDb = db.getSiblingDB(\"" + << escape(parsedURI.getAuthenticationDatabase()) << "\");" << endl; authStringStream << "authDb._authOrThrow({ "; - if (!shellGlobalParams.username.empty()) { + if (!parsedURI.getUser().empty()) { authStringStream << saslCommandUserFieldName << ": username "; } - if (shellGlobalParams.usingPassword) { + if (usingPassword) { authStringStream << ", " << saslCommandPasswordFieldName << ": password "; } - if (!shellGlobalParams.gssapiHostName.empty()) { + if (const auto gssapiHostNameKey = parsedURI.getOption("gssapiHostName")) { authStringStream << ", " << saslCommandServiceHostnameFieldName << ": \"" - << escape(shellGlobalParams.gssapiHostName) << '"' << endl; + << escape(gssapiHostNameKey.get()) << '"' << endl; } authStringStream << "});" << endl; } diff --git a/src/mongo/shell/mongo.js b/src/mongo/shell/mongo.js index 9a2f21544ef..eb5a1c13843 100644 --- a/src/mongo/shell/mongo.js +++ b/src/mongo/shell/mongo.js @@ -318,7 +318,13 @@ connect = function(url, user, pass) { } } - chatty("connecting to: " + url); + var atPos = url.indexOf("@"); + var protocolPos = url.indexOf("://"); + var safeURL = url; + if (atPos != -1 && protocolPos != -1) { + safeURL = url.substring(0, protocolPos + 3) + url.substring(atPos + 1); + } + chatty("connecting to: " + safeURL); var m = new Mongo(url); var db = m.getDB(m.defaultDB); diff --git a/src/mongo/shell/shell_options.cpp b/src/mongo/shell/shell_options.cpp index b92ad6f5637..b847e55d711 100644 --- a/src/mongo/shell/shell_options.cpp +++ b/src/mongo/shell/shell_options.cpp @@ -323,7 +323,6 @@ Status storeMongoShellOptions(const moe::Environment& params, } if (params.count("password")) { - shellGlobalParams.usingPassword = true; shellGlobalParams.password = params["password"].as<string>(); } diff --git a/src/mongo/shell/shell_options.h b/src/mongo/shell/shell_options.h index d28bf44d55f..76b4f25a913 100644 --- a/src/mongo/shell/shell_options.h +++ b/src/mongo/shell/shell_options.h @@ -54,7 +54,6 @@ struct ShellGlobalParams { std::string username; std::string password; - bool usingPassword; std::string authenticationMechanism; std::string authenticationDatabase; std::string gssapiServiceName; |