diff --git a/jstests/auth/sasl_mechanism_discovery.js b/jstests/auth/sasl_mechanism_discovery.js
new file mode 100644
index 0000000000000..c2dfb7aabcc1c
--- /dev/null
+++ b/jstests/auth/sasl_mechanism_discovery.js
@@ -0,0 +1,36 @@
+// Tests that a client may discover a user's supported SASL mechanisms via isMaster.
+(function() {
+ "use strict";
+
+ function runTest(conn) {
+ var db = conn.getDB("admin");
+ var externalDB = conn.getDB("$external");
+
+ // Make users
+ assert.commandWorked(db.runCommand({createUser: "user", pwd: "pwd", roles: []}));
+ assert.commandWorked(externalDB.runCommand({createUser: "user", roles: []}));
+
+ // Internal users should support SCRAM-SHA-1.
+ var isMasterResult =
+ assert.commandWorked(db.runCommand({isMaster: 1, saslSupportedMechs: "admin.user"}));
+ assert.eq(["SCRAM-SHA-1"], isMasterResult.saslSupportedMechs, tojson(isMasterResult));
+
+ // External users should support PLAIN, but not SCRAM-SHA-1.
+ isMasterResult = assert.commandWorked(
+ db.runCommand({isMaster: 1, saslSupportedMechs: "$external.user"}));
+ assert.eq(["PLAIN"], isMasterResult.saslSupportedMechs, tojson(isMasterResult));
+ }
+
+ // Test standalone.
+ var m = MongoRunner.runMongod({setParameter: "authenticationMechanisms=SCRAM-SHA-1,PLAIN"});
+ runTest(m);
+ MongoRunner.stopMongod(m);
+
+ // Test mongos.
+ var st = new ShardingTest({
+ shards: 0,
+ other: {mongosOptions: {setParameter: "authenticationMechanisms=PLAIN,SCRAM-SHA-1"}}
+ });
+ runTest(st.s0);
+ st.stop();
+})();
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index e1bfbbabeac57..9be76b3c20dcc 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -61,6 +61,7 @@ env.Library(
'role_graph.cpp',
'role_graph_update.cpp',
'role_graph_builtin_roles.cpp',
+ 'sasl_mechanism_advertiser.cpp',
'user.cpp',
'user_document_parser.cpp',
'user_management_commands_parser.cpp',
diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp
new file mode 100644
index 0000000000000..75f6aa191edc2
--- /dev/null
+++ b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp
@@ -0,0 +1,80 @@
+/**
+ * Copyright (C) 2018 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 "mongo/db/auth/sasl_mechanism_advertiser.h"
+
+#include "mongo/crypto/sha1_block.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/sasl_options.h"
+#include "mongo/db/auth/user.h"
+
+namespace mongo {
+
+namespace {
+void appendMechanismIfSupported(StringData mechanism, BSONArrayBuilder* builder) {
+ const auto& globalMechanisms = saslGlobalParams.authenticationMechanisms;
+ if (std::find(globalMechanisms.begin(), globalMechanisms.end(), mechanism) !=
+ globalMechanisms.end()) {
+ (*builder) << mechanism;
+ }
+}
+} // namespace
+
+
+void SASLMechanismAdvertiser::advertise(OperationContext* opCtx,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
+ BSONElement saslSupportedMechs = cmdObj["saslSupportedMechs"];
+ if (saslSupportedMechs.type() == BSONType::String) {
+ AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext());
+
+ UserName userName = uassertStatusOK(UserName::parse(saslSupportedMechs.String()));
+
+ User* userObj;
+ Status status = authManager->acquireUser(opCtx, userName, &userObj);
+ uassertStatusOK(status);
+ auto credentials = userObj->getCredentials();
+ authManager->releaseUser(userObj);
+
+ BSONArrayBuilder mechanismsBuilder;
+ if (credentials.isExternal) {
+ for (const StringData& userMechanism : {"GSSAPI", "PLAIN"}) {
+ appendMechanismIfSupported(userMechanism, &mechanismsBuilder);
+ }
+ } else if (credentials.scram().isValid()) {
+ appendMechanismIfSupported("SCRAM-SHA-1", &mechanismsBuilder);
+ }
+
+ result->appendArray("saslSupportedMechs", mechanismsBuilder.arr());
+ }
+}
+
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.h b/src/mongo/db/auth/sasl_mechanism_advertiser.h
new file mode 100644
index 0000000000000..cae0e65895deb
--- /dev/null
+++ b/src/mongo/db/auth/sasl_mechanism_advertiser.h
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/operation_context.h"
+
+namespace mongo {
+
+class SASLMechanismAdvertiser {
+public:
+ static void advertise(OperationContext* opCtx, const BSONObj& cmdObj, BSONObjBuilder* result);
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/user_name.cpp b/src/mongo/db/auth/user_name.cpp
index 7b56a87dbd744..3bcdf3ad813f8 100644
--- a/src/mongo/db/auth/user_name.cpp
+++ b/src/mongo/db/auth/user_name.cpp
@@ -47,6 +47,21 @@ UserName::UserName(StringData user, StringData dbname) {
_splitPoint = user.size();
}
+
+StatusWith UserName::parse(StringData userNameStr) {
+ size_t splitPoint = userNameStr.find('.');
+
+ if (splitPoint == std::string::npos) {
+ return Status(ErrorCodes::BadValue,
+ "username must contain a '.' separated database.user pair");
+ }
+
+ StringData userDBPortion = userNameStr.substr(0, splitPoint);
+ StringData userNamePortion = userNameStr.substr(splitPoint + 1);
+
+ return UserName(userNamePortion, userDBPortion);
+}
+
std::ostream& operator<<(std::ostream& os, const UserName& name) {
return os << name.getFullName();
}
diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h
index 97a09270115fd..260604d6a18db 100644
--- a/src/mongo/db/auth/user_name.h
+++ b/src/mongo/db/auth/user_name.h
@@ -33,6 +33,7 @@
#include "mongo/base/disallow_copying.h"
+#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
namespace mongo {
@@ -47,6 +48,11 @@ class UserName {
UserName() : _splitPoint(0) {}
UserName(StringData user, StringData dbname);
+ /**
+ * Parses a string of the form "db.username" into a UserName object.
+ */
+ static StatusWith parse(StringData userNameStr);
+
/**
* Gets the user part of a UserName.
*/
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index 0911a180d36db..d69d6b362885e 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -33,6 +33,7 @@
#include
#include "mongo/client/connpool.h"
+#include "mongo/db/auth/sasl_mechanism_advertiser.h"
#include "mongo/db/client.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/db_raii.h"
@@ -389,6 +390,8 @@ class CmdIsMaster : public BasicCommand {
.serverNegotiate(cmdObj, &result);
}
+ SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result);
+
return true;
}
} cmdismaster;
diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp
index 441648bf7e9cc..531f758859fbd 100644
--- a/src/mongo/s/commands/cluster_is_master_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp
@@ -28,6 +28,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/db/auth/sasl_mechanism_advertiser.h"
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
#include "mongo/db/logical_session_id.h"
@@ -132,6 +133,8 @@ class CmdIsMaster : public BasicCommand {
MessageCompressorManager::forSession(opCtx->getClient()->session())
.serverNegotiate(cmdObj, &result);
+ SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result);
+
return true;
}