summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/sasl_mechanism_discovery.js12
-rw-r--r--jstests/auth/system_user_exception.js21
-rw-r--r--src/mongo/db/auth/SConscript39
-rw-r--r--src/mongo/db/auth/authentication_session.h22
-rw-r--r--src/mongo/db/auth/native_sasl_authentication_session.cpp164
-rw-r--r--src/mongo/db/auth/sasl_authentication_session.cpp92
-rw-r--r--src/mongo/db/auth/sasl_authentication_session.h180
-rw-r--r--src/mongo/db/auth/sasl_authentication_session_test.cpp89
-rw-r--r--src/mongo/db/auth/sasl_commands.cpp151
-rw-r--r--src/mongo/db/auth/sasl_mechanism_advertiser.cpp125
-rw-r--r--src/mongo/db/auth/sasl_mechanism_advertiser.h42
-rw-r--r--src/mongo/db/auth/sasl_mechanism_policies.h (renamed from src/mongo/db/auth/native_sasl_authentication_session.h)70
-rw-r--r--src/mongo/db/auth/sasl_mechanism_registry.cpp179
-rw-r--r--src/mongo/db/auth/sasl_mechanism_registry.h336
-rw-r--r--src/mongo/db/auth/sasl_mechanism_registry_test.cpp350
-rw-r--r--src/mongo/db/auth/sasl_plain_server_conversation.cpp61
-rw-r--r--src/mongo/db/auth/sasl_plain_server_conversation.h39
-rw-r--r--src/mongo/db/auth/sasl_scram_server_conversation.cpp100
-rw-r--r--src/mongo/db/auth/sasl_scram_server_conversation.h130
-rw-r--r--src/mongo/db/auth/sasl_scram_test.cpp142
-rw-r--r--src/mongo/db/auth/sasl_server_conversation.cpp41
-rw-r--r--src/mongo/db/auth/sasl_server_conversation.h89
-rw-r--r--src/mongo/db/repl/SConscript1
-rw-r--r--src/mongo/db/repl/replication_info.cpp5
-rw-r--r--src/mongo/s/commands/cluster_is_master_cmd.cpp5
25 files changed, 1330 insertions, 1155 deletions
diff --git a/jstests/auth/sasl_mechanism_discovery.js b/jstests/auth/sasl_mechanism_discovery.js
index 65f930dbce9..58ccb011090 100644
--- a/jstests/auth/sasl_mechanism_discovery.js
+++ b/jstests/auth/sasl_mechanism_discovery.js
@@ -12,7 +12,7 @@
function checkMechs(userid, mechs) {
const res =
assert.commandWorked(db.runCommand({isMaster: 1, saslSupportedMechs: userid}));
- assert.eq(mechs, res.saslSupportedMechs, tojson(res));
+ assert.eq(mechs.sort(), res.saslSupportedMechs.sort(), tojson(res));
}
// Make users.
@@ -22,10 +22,14 @@
{createUser: "IX", pwd: "pwd", roles: [], mechanisms: ["SCRAM-SHA-256"]}));
// Internal users should support scram methods.
- checkMechs("admin.user", ["SCRAM-SHA-1", "SCRAM-SHA-256"]);
+ checkMechs("admin.user", ["SCRAM-SHA-256", "SCRAM-SHA-1"]);
- // External users should support PLAIN, but not scram methods.
- checkMechs("$external.user", ["PLAIN"]);
+ // External users on enterprise should support PLAIN, but not scram methods.
+ if (assert.commandWorked(db.runCommand({buildInfo: 1})).modules.includes("enterprise")) {
+ checkMechs("$external.user", ["PLAIN"]);
+ } else {
+ checkMechs("$external.user", []);
+ }
// Check non-normalized name finds normalized user.
const IXchar = "\u2168";
diff --git a/jstests/auth/system_user_exception.js b/jstests/auth/system_user_exception.js
new file mode 100644
index 00000000000..5955d629135
--- /dev/null
+++ b/jstests/auth/system_user_exception.js
@@ -0,0 +1,21 @@
+// Test the special handling of the __system user
+// works when the SCRAM-SHA-1 pw auth mechanisms are disabled.
+(function() {
+ "use strict";
+
+ // Start mongod with no authentication mechanisms enabled
+ var m = MongoRunner.runMongod(
+ {keyFile: "jstests/libs/key1", setParameter: "authenticationMechanisms=PLAIN"});
+
+ // Verify that it's possible to use SCRAM-SHA-1 to authenticate as the __system@local user
+ assert.eq(
+ 1, m.getDB("local").auth({user: "__system", pwd: "foopdedoop", mechanism: "SCRAM-SHA-1"}));
+
+ // Verify that it is not possible to authenticate other users
+ m.getDB("test").runCommand(
+ {createUser: "guest", pwd: "guest", roles: jsTest.readOnlyUserRoles});
+ assert.eq(0, m.getDB("test").auth({user: "guest", pwd: "guest", mechanism: "SCRAM-SHA-1"}));
+
+ MongoRunner.stopMongod(m);
+
+})();
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index 6ea06b5dddd..a13216efbb2 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -60,7 +60,6 @@ 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',
@@ -147,7 +146,6 @@ env.Library('authservercommon',
'$BUILD_DIR/mongo/db/commands/core',
'authcommon',
'authcore',
- 'authmocks',
'authorization_manager_global',
'saslauth',
'security_file',
@@ -170,14 +168,13 @@ env.Library('sasl_options',
)
env.Library('saslauth',
- ['native_sasl_authentication_session.cpp',
- 'sasl_authentication_session.cpp',
- 'sasl_plain_server_conversation.cpp',
- 'sasl_scram_server_conversation.cpp',
- 'sasl_server_conversation.cpp'],
+ [
+ 'sasl_mechanism_registry.cpp',
+ 'sasl_plain_server_conversation.cpp',
+ 'sasl_scram_server_conversation.cpp',
+ ],
LIBDEPS=[
'authcore',
- 'authmocks', # Wat?
'sasl_options',
'$BUILD_DIR/mongo/base/secure_allocator',
'$BUILD_DIR/mongo/db/commands/test_commands_enabled',
@@ -186,6 +183,16 @@ env.Library('saslauth',
],
)
+env.CppUnitTest(target='sasl_mechanism_registry_test',
+ source=[
+ 'sasl_mechanism_registry_test.cpp',
+ ],
+ LIBDEPS=[
+ 'authmocks',
+ 'saslauth',
+ '$BUILD_DIR/mongo/db/service_context_noop_init',
+ ])
+
env.Library('authmongod',
['authz_manager_external_state_d.cpp',
'authz_session_external_state_d.cpp',
@@ -226,22 +233,22 @@ env.Library(
)
env.CppUnitTest('action_set_test', 'action_set_test.cpp',
- LIBDEPS=['authcore', 'authmocks', 'saslauth'])
+ LIBDEPS=['authcore', 'authmocks'])
env.CppUnitTest('privilege_parser_test', 'privilege_parser_test.cpp',
- LIBDEPS=['authcore', 'authmocks', 'saslauth'])
+ LIBDEPS=['authcore', 'authmocks'])
env.CppUnitTest('role_graph_test', 'role_graph_test.cpp',
- LIBDEPS=['authcore', 'authmocks', 'saslauth'])
+ LIBDEPS=['authcore', 'authmocks'])
env.CppUnitTest('user_document_parser_test', 'user_document_parser_test.cpp',
- LIBDEPS=['authcore', 'authmocks', 'saslauth'])
+ LIBDEPS=['authcore', 'authmocks'])
env.CppUnitTest('user_set_test', 'user_set_test.cpp',
- LIBDEPS=['authcore', 'authmocks', 'saslauth'])
+ LIBDEPS=['authcore', 'authmocks'])
env.CppUnitTest('authorization_manager_test', 'authorization_manager_test.cpp',
LIBDEPS=[
'$BUILD_DIR/mongo/transport/transport_layer_common',
'$BUILD_DIR/mongo/transport/transport_layer_mock',
'authcore',
- 'authmocks',
- 'saslauth'])
+ 'authmocks'
+ ])
env.Library(
target='authorization_session_for_test',
@@ -300,8 +307,10 @@ env.CppUnitTest(
'sasl_scram_test.cpp',
],
LIBDEPS_PRIVATE=[
+ 'authmocks',
'saslauth',
'$BUILD_DIR/mongo/client/sasl_client',
+ '$BUILD_DIR/mongo/db/service_context_noop_init',
],
)
diff --git a/src/mongo/db/auth/authentication_session.h b/src/mongo/db/auth/authentication_session.h
index ed7404dc58d..c4d0f464659 100644
--- a/src/mongo/db/auth/authentication_session.h
+++ b/src/mongo/db/auth/authentication_session.h
@@ -30,23 +30,21 @@
#include <memory>
#include "mongo/base/disallow_copying.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
namespace mongo {
class Client;
/**
- * Abstract type representing an ongoing authentication session.
- *
- * An example subclass is MongoAuthenticationSession.
+ * Type representing an ongoing authentication session.
*/
class AuthenticationSession {
MONGO_DISALLOW_COPYING(AuthenticationSession);
public:
- enum SessionType {
- SESSION_TYPE_SASL // SASL authentication mechanism.
- };
+ explicit AuthenticationSession(std::unique_ptr<ServerMechanismBase> mech)
+ : _mech(std::move(mech)) {}
/**
* Sets the authentication session for the given "client" to "newSession".
@@ -58,21 +56,17 @@ public:
*/
static void swap(Client* client, std::unique_ptr<AuthenticationSession>& other);
- virtual ~AuthenticationSession() = default;
-
/**
* Return an identifer of the type of session, so that a caller can safely cast it and
* extract the type-specific data stored within.
*/
- SessionType getType() const {
- return _sessionType;
+ ServerMechanismBase& getMechanism() const {
+ invariant(_mech);
+ return *_mech;
}
-protected:
- explicit AuthenticationSession(SessionType sessionType) : _sessionType(sessionType) {}
-
private:
- const SessionType _sessionType;
+ std::unique_ptr<ServerMechanismBase> _mech;
};
} // namespace mongo
diff --git a/src/mongo/db/auth/native_sasl_authentication_session.cpp b/src/mongo/db/auth/native_sasl_authentication_session.cpp
deleted file mode 100644
index 74b7688f265..00000000000
--- a/src/mongo/db/auth/native_sasl_authentication_session.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
- *
- * 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.
- */
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl
-
-#include "mongo/db/auth/native_sasl_authentication_session.h"
-
-#include <boost/range/size.hpp>
-
-#include "mongo/base/init.h"
-#include "mongo/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/bson/util/bson_extract.h"
-#include "mongo/client/sasl_client_authenticate.h"
-#include "mongo/db/auth/authorization_manager.h"
-#include "mongo/db/auth/authorization_manager_global.h"
-#include "mongo/db/auth/authorization_session.h"
-#include "mongo/db/auth/authz_manager_external_state_mock.h"
-#include "mongo/db/auth/authz_session_external_state_mock.h"
-#include "mongo/db/auth/sasl_options.h"
-#include "mongo/db/auth/sasl_plain_server_conversation.h"
-#include "mongo/db/auth/sasl_scram_server_conversation.h"
-#include "mongo/db/commands.h"
-#include "mongo/stdx/memory.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/mongoutils/str.h"
-#include "mongo/util/net/sock.h"
-
-namespace mongo {
-
-using std::unique_ptr;
-
-namespace {
-SaslAuthenticationSession* createNativeSaslAuthenticationSession(AuthorizationSession* authzSession,
- StringData db,
- StringData mechanism) {
- return new NativeSaslAuthenticationSession(authzSession);
-}
-
-MONGO_INITIALIZER(NativeSaslServerCore)(InitializerContext* context) {
- if (saslGlobalParams.hostName.empty())
- saslGlobalParams.hostName = getHostNameCached();
- if (saslGlobalParams.serviceName.empty())
- saslGlobalParams.serviceName = "mongodb";
-
- SaslAuthenticationSession::create = createNativeSaslAuthenticationSession;
- return Status::OK();
-}
-
-// PostSaslCommands is reversely dependent on CyrusSaslCommands having been run
-MONGO_INITIALIZER_WITH_PREREQUISITES(PostSaslCommands, ("NativeSaslServerCore"))
-(InitializerContext*) {
- AuthorizationManager authzManager(stdx::make_unique<AuthzManagerExternalStateMock>());
- std::unique_ptr<AuthorizationSession> authzSession = authzManager.makeAuthorizationSession();
-
- for (size_t i = 0; i < saslGlobalParams.authenticationMechanisms.size(); ++i) {
- const std::string& mechanism = saslGlobalParams.authenticationMechanisms[i];
- if (mechanism == "SCRAM-SHA-1" || mechanism == "SCRAM-SHA-256" ||
- mechanism == "MONGODB-X509") {
- // Not a SASL mechanism; no need to smoke test built-in mechanisms.
- continue;
- }
- unique_ptr<SaslAuthenticationSession> session(
- SaslAuthenticationSession::create(authzSession.get(), "$external", mechanism));
- Status status = session->start(
- "test", mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, true);
- if (!status.isOK())
- return status;
- }
-
- return Status::OK();
-}
-} // namespace
-
-NativeSaslAuthenticationSession::NativeSaslAuthenticationSession(AuthorizationSession* authzSession)
- : SaslAuthenticationSession(authzSession), _mechanism("") {}
-
-NativeSaslAuthenticationSession::~NativeSaslAuthenticationSession() {}
-
-Status NativeSaslAuthenticationSession::start(StringData authenticationDatabase,
- StringData mechanism,
- StringData serviceName,
- StringData serviceHostname,
- int64_t conversationId,
- bool autoAuthorize) {
- fassert(18626, conversationId > 0);
-
- if (_conversationId != 0) {
- return Status(ErrorCodes::AlreadyInitialized,
- "Cannot call start() twice on same NativeSaslAuthenticationSession.");
- }
-
- _authenticationDatabase = authenticationDatabase.toString();
- _mechanism = mechanism.toString();
- _serviceName = serviceName.toString();
- _serviceHostname = serviceHostname.toString();
- _conversationId = conversationId;
- _autoAuthorize = autoAuthorize;
-
- if (mechanism == "PLAIN") {
- _saslConversation.reset(new SaslPLAINServerConversation(this));
- } else if (mechanism == "SCRAM-SHA-1") {
- _saslConversation.reset(new SaslSCRAMServerConversationImpl<SHA1Block>(this));
- } else if (mechanism == "SCRAM-SHA-256") {
- _saslConversation.reset(new SaslSCRAMServerConversationImpl<SHA256Block>(this));
- } else {
- return Status(ErrorCodes::BadValue,
- mongoutils::str::stream() << "SASL mechanism " << mechanism
- << " is not supported");
- }
-
- return Status::OK();
-}
-
-Status NativeSaslAuthenticationSession::step(StringData inputData, std::string* outputData) {
- if (!_saslConversation) {
- return Status(ErrorCodes::BadValue,
- mongoutils::str::stream()
- << "The authentication session has not been properly initialized");
- }
-
- StatusWith<bool> status = _saslConversation->step(inputData, outputData);
- if (status.isOK()) {
- _done = status.getValue();
- } else {
- _done = true;
- }
- return status.getStatus();
-}
-
-std::string NativeSaslAuthenticationSession::getPrincipalId() const {
- return _saslConversation->getPrincipalId();
-}
-
-const char* NativeSaslAuthenticationSession::getMechanism() const {
- return _mechanism.c_str();
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_authentication_session.cpp b/src/mongo/db/auth/sasl_authentication_session.cpp
deleted file mode 100644
index 5195a6aa193..00000000000
--- a/src/mongo/db/auth/sasl_authentication_session.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 10gen, 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 <http://www.gnu.org/licenses/>.
- *
- * 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.
- */
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl
-
-#include "mongo/db/auth/sasl_authentication_session.h"
-
-#include <boost/range/size.hpp>
-
-#include "mongo/base/init.h"
-#include "mongo/base/string_data.h"
-#include "mongo/bson/util/bson_extract.h"
-#include "mongo/client/sasl_client_authenticate.h"
-#include "mongo/db/auth/authorization_manager.h"
-#include "mongo/db/auth/authorization_manager_global.h"
-#include "mongo/db/auth/authorization_session.h"
-#include "mongo/db/auth/authz_manager_external_state_mock.h"
-#include "mongo/db/auth/authz_session_external_state_mock.h"
-#include "mongo/db/commands.h"
-#include "mongo/util/assert_util.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-SaslAuthenticationSession::SaslAuthenticationSessionFactoryFn SaslAuthenticationSession::create;
-
-// Mechanism name constants.
-const char SaslAuthenticationSession::mechanismSCRAMSHA256[] = "SCRAM-SHA-256";
-const char SaslAuthenticationSession::mechanismSCRAMSHA1[] = "SCRAM-SHA-1";
-const char SaslAuthenticationSession::mechanismGSSAPI[] = "GSSAPI";
-const char SaslAuthenticationSession::mechanismPLAIN[] = "PLAIN";
-
-/**
- * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser."
- *
- * The standard rule in MongoDB is simple. The authenticated user name must be the same as the
- * requested user name.
- */
-bool isAuthorizedCommon(SaslAuthenticationSession* session,
- StringData requestedUser,
- StringData authenticatedUser) {
- return requestedUser == authenticatedUser;
-}
-
-SaslAuthenticationSession::SaslAuthenticationSession(AuthorizationSession* authzSession)
- : AuthenticationSession(AuthenticationSession::SESSION_TYPE_SASL),
- _opCtx(nullptr),
- _authzSession(authzSession),
- _saslStep(0),
- _conversationId(0),
- _autoAuthorize(false),
- _done(false) {}
-
-SaslAuthenticationSession::~SaslAuthenticationSession(){};
-
-StringData SaslAuthenticationSession::getAuthenticationDatabase() const {
- if (Command::testCommandsEnabled && _authenticationDatabase == "admin" &&
- getPrincipalId() == internalSecurity.user->getName().getUser()) {
- // Allows authenticating as the internal user against the admin database. This is to
- // support the auth passthrough test framework on mongos (since you can't use the local
- // database on a mongos, so you can't auth as the internal user without this).
- return internalSecurity.user->getName().getDB();
- } else {
- return _authenticationDatabase;
- }
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_authentication_session.h b/src/mongo/db/auth/sasl_authentication_session.h
deleted file mode 100644
index c28e60919e8..00000000000
--- a/src/mongo/db/auth/sasl_authentication_session.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2012 10gen, 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 <http://www.gnu.org/licenses/>.
- *
- * 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 <cstdint>
-#include <string>
-#include <vector>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/db/auth/authentication_session.h"
-#include "mongo/db/auth/authorization_session.h"
-#include "mongo/stdx/functional.h"
-
-namespace mongo {
-
-class AuthorizationSession;
-class OperationContext;
-
-/**
- * Authentication session data for the server side of SASL authentication.
- */
-class SaslAuthenticationSession : public AuthenticationSession {
- MONGO_DISALLOW_COPYING(SaslAuthenticationSession);
-
-public:
- typedef stdx::function<SaslAuthenticationSession*(
- AuthorizationSession*, StringData, StringData)>
- SaslAuthenticationSessionFactoryFn;
- static SaslAuthenticationSessionFactoryFn create;
-
- // Mechanism name constants.
- static const char mechanismSCRAMSHA256[];
- static const char mechanismSCRAMSHA1[];
- static const char mechanismGSSAPI[];
- static const char mechanismPLAIN[];
-
- explicit SaslAuthenticationSession(AuthorizationSession* authSession);
- virtual ~SaslAuthenticationSession();
-
- /**
- * Start the server side of a SASL authentication.
- *
- * "authenticationDatabase" is the database against which the user is authenticating.
- * "mechanism" is the SASL mechanism to use.
- * "serviceName" is the SASL service name to use.
- * "serviceHostname" is the FQDN of this server.
- * "conversationId" is the conversation identifier to use for this session.
- *
- * If "autoAuthorize" is set to true, the server will automatically acquire all privileges
- * for a successfully authenticated user. If it is false, the client will need to
- * explicilty acquire privileges on resources it wishes to access.
- *
- * Must be called only once on an instance.
- */
- virtual Status start(StringData authenticationDatabase,
- StringData mechanism,
- StringData serviceName,
- StringData serviceHostname,
- int64_t conversationId,
- bool autoAuthorize) = 0;
-
- /**
- * Perform one step of the server side of the authentication session,
- * consuming "inputData" and producing "*outputData".
- *
- * A return of Status::OK() indiciates succesful progress towards authentication.
- * Any other return code indicates that authentication has failed.
- *
- * Must not be called before start().
- */
- virtual Status step(StringData inputData, std::string* outputData) = 0;
-
- /**
- * Returns the the operation context associated with the currently executing command.
- * Authentication commands must set this on their associated
- * SaslAuthenticationSession.
- */
- OperationContext* getOpCtxt() const {
- return _opCtx;
- }
- void setOpCtxt(OperationContext* opCtx) {
- _opCtx = opCtx;
- }
-
- /**
- * Gets the name of the database against which this authentication conversation is running.
- *
- * Not meaningful before a successful call to start().
- */
- StringData getAuthenticationDatabase() const;
-
- /**
- * Get the conversation id for this authentication session.
- *
- * Must not be called before start().
- */
- int64_t getConversationId() const {
- return _conversationId;
- }
-
- /**
- * If the last call to step() returned Status::OK(), this method returns true if the
- * authentication conversation has completed, from the server's perspective. If it returns
- * false, the server expects more input from the client. If the last call to step() did not
- * return Status::OK(), returns true.
- *
- * Behavior is undefined if step() has not been called.
- */
- bool isDone() const {
- return _done;
- }
-
- /**
- * Gets the string identifier of the principal being authenticated.
- *
- * Returns the empty string if the session does not yet know the identity being
- * authenticated.
- */
- virtual std::string getPrincipalId() const = 0;
-
- /**
- * Gets the name of the SASL mechanism in use.
- *
- * Returns "" if start() has not been called or if start() did not return Status::OK().
- */
- virtual const char* getMechanism() const = 0;
-
- /**
- * Returns true if automatic privilege acquisition should be used for this principal, after
- * authentication. Not meaningful before a successful call to start().
- */
- bool shouldAutoAuthorize() const {
- return _autoAuthorize;
- }
-
- AuthorizationSession* getAuthorizationSession() {
- return _authzSession;
- }
-
-protected:
- OperationContext* _opCtx;
- AuthorizationSession* _authzSession;
- std::string _authenticationDatabase;
- std::string _serviceName;
- std::string _serviceHostname;
- int _saslStep;
- int64_t _conversationId;
- bool _autoAuthorize;
- bool _done;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_authentication_session_test.cpp b/src/mongo/db/auth/sasl_authentication_session_test.cpp
index 7226395ece6..4babe8c816b 100644
--- a/src/mongo/db/auth/sasl_authentication_session_test.cpp
+++ b/src/mongo/db/auth/sasl_authentication_session_test.cpp
@@ -16,10 +16,13 @@
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/authz_manager_external_state_mock.h"
#include "mongo/db/auth/authz_session_external_state_mock.h"
-#include "mongo/db/auth/sasl_authentication_session.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/auth/sasl_options.h"
+#include "mongo/db/auth/sasl_plain_server_conversation.h"
+#include "mongo/db/auth/sasl_scram_server_conversation.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/operation_context_noop.h"
+#include "mongo/db/service_context_noop.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/log.h"
#include "mongo/util/password_digest.h"
@@ -38,12 +41,16 @@ public:
void testWrongClientMechanism();
void testWrongServerMechanism();
+ ServiceContextNoop serviceContext;
+ ServiceContext::UniqueClient opClient;
+ ServiceContext::UniqueOperationContext opCtx;
AuthzManagerExternalStateMock* authManagerExternalState;
- AuthorizationManager authManager;
+ AuthorizationManager* authManager;
std::unique_ptr<AuthorizationSession> authSession;
+ SASLServerMechanismRegistry registry;
std::string mechanism;
std::unique_ptr<SaslClientSession> client;
- std::unique_ptr<SaslAuthenticationSession> server;
+ std::unique_ptr<ServerMechanismBase> server;
private:
void assertConversationFailure();
@@ -58,17 +65,27 @@ const std::string mockServiceName = "mocksvc";
const std::string mockHostName = "host.mockery.com";
SaslConversation::SaslConversation(std::string mech)
- : authManagerExternalState(new AuthzManagerExternalStateMock),
- authManager(std::unique_ptr<AuthzManagerExternalState>(authManagerExternalState)),
- authSession(authManager.makeAuthorizationSession()),
+ : opClient(serviceContext.makeClient("saslTest")),
+ opCtx(serviceContext.makeOperationContext(opClient.get())),
+ authManagerExternalState(new AuthzManagerExternalStateMock),
+ authManager(new AuthorizationManager(
+ std::unique_ptr<AuthzManagerExternalState>(authManagerExternalState))),
+ authSession(authManager->makeAuthorizationSession()),
mechanism(mech) {
- OperationContextNoop opCtx;
+
+ AuthorizationManager::set(&serviceContext, std::unique_ptr<AuthorizationManager>(authManager));
client.reset(SaslClientSession::create(mechanism));
- server.reset(SaslAuthenticationSession::create(authSession.get(), "test", mechanism));
+
+ registry.registerFactory<PLAINServerFactory>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+ registry.registerFactory<SCRAMSHA1ServerFactory>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+ registry.registerFactory<SCRAMSHA256ServerFactory>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
ASSERT_OK(authManagerExternalState->updateOne(
- &opCtx,
+ opCtx.get(),
AuthorizationManager::versionCollectionNamespace,
AuthorizationManager::versionDocumentQuery,
BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName
@@ -88,7 +105,7 @@ SaslConversation::SaslConversation(std::string mech)
<< scram::Secrets<SHA256Block>::generateCredentials(
"frim", saslGlobalParams.scramSHA256IterationCount.load()));
- ASSERT_OK(authManagerExternalState->insert(&opCtx,
+ ASSERT_OK(authManagerExternalState->insert(opCtx.get(),
NamespaceString("admin.system.users"),
BSON("_id"
<< "test.andy"
@@ -107,16 +124,16 @@ void SaslConversation::assertConversationFailure() {
std::string clientMessage;
std::string serverMessage;
Status clientStatus(ErrorCodes::InternalError, "");
- Status serverStatus(ErrorCodes::InternalError, "");
+ StatusWith<std::string> serverResponse("");
do {
- clientStatus = client->step(serverMessage, &clientMessage);
+ clientStatus = client->step(serverResponse.getValue(), &clientMessage);
if (!clientStatus.isOK())
break;
- serverStatus = server->step(clientMessage, &serverMessage);
- if (!serverStatus.isOK())
+ serverResponse = server->step(opCtx.get(), clientMessage);
+ if (!serverResponse.isOK())
break;
} while (!client->isDone());
- ASSERT_FALSE(serverStatus.isOK() && clientStatus.isOK() && client->isDone() &&
+ ASSERT_FALSE(serverResponse.isOK() && clientStatus.isOK() && client->isDone() &&
server->isDone());
}
@@ -128,13 +145,12 @@ void SaslConversation::testSuccessfulAuthentication() {
client->setParameter(SaslClientSession::parameterPassword, "frim");
ASSERT_OK(client->initialize());
- ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true));
-
std::string clientMessage;
- std::string serverMessage;
+ StatusWith<std::string> serverResponse("");
do {
- ASSERT_OK(client->step(serverMessage, &clientMessage));
- ASSERT_OK(server->step(clientMessage, &serverMessage));
+ ASSERT_OK(client->step(serverResponse.getValue(), &clientMessage));
+ serverResponse = server->step(opCtx.get(), clientMessage);
+ ASSERT_OK(serverResponse.getStatus());
} while (!client->isDone());
ASSERT_TRUE(server->isDone());
}
@@ -147,8 +163,6 @@ void SaslConversation::testNoSuchUser() {
client->setParameter(SaslClientSession::parameterPassword, "frim");
ASSERT_OK(client->initialize());
- ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true));
-
assertConversationFailure();
}
@@ -160,8 +174,6 @@ void SaslConversation::testBadPassword() {
client->setParameter(SaslClientSession::parameterPassword, "WRONG");
ASSERT_OK(client->initialize());
- ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true));
-
assertConversationFailure();
}
@@ -175,8 +187,6 @@ void SaslConversation::testWrongClientMechanism() {
client->setParameter(SaslClientSession::parameterPassword, "frim");
ASSERT_OK(client->initialize());
- ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true));
-
assertConversationFailure();
}
@@ -188,19 +198,22 @@ void SaslConversation::testWrongServerMechanism() {
client->setParameter(SaslClientSession::parameterPassword, "frim");
ASSERT_OK(client->initialize());
- ASSERT_OK(server->start("test",
- mechanism != "SCRAM-SHA-1" ? "SCRAM-SHA-1" : "PLAIN",
- mockServiceName,
- mockHostName,
- 1,
- true));
+ auto swServer =
+ registry.getServerMechanism(mechanism != "SCRAM-SHA-1" ? "SCRAM-SHA-1" : "PLAIN", "test");
+ ASSERT_OK(swServer.getStatus());
+ server = std::move(swServer.getValue());
+
assertConversationFailure();
}
-#define DEFINE_MECHANISM_FIXTURE(CLASS_SUFFIX, MECH_NAME) \
- class SaslConversation##CLASS_SUFFIX : public SaslConversation { \
- public: \
- SaslConversation##CLASS_SUFFIX() : SaslConversation(MECH_NAME) {} \
+#define DEFINE_MECHANISM_FIXTURE(CLASS_SUFFIX, MECH_NAME) \
+ class SaslConversation##CLASS_SUFFIX : public SaslConversation { \
+ public: \
+ SaslConversation##CLASS_SUFFIX() : SaslConversation(MECH_NAME) { \
+ auto swServer = registry.getServerMechanism(MECH_NAME, "test"); \
+ ASSERT_OK(swServer.getStatus()); \
+ server = std::move(swServer.getValue()); \
+ } \
}
#define DEFINE_MECHANISM_TEST(FIXTURE_NAME, TEST_NAME) \
@@ -236,7 +249,9 @@ TEST_F(SaslIllegalConversation, IllegalClientMechanism) {
}
TEST_F(SaslIllegalConversation, IllegalServerMechanism) {
- ASSERT_NOT_OK(server->start("test", "FAKE", mockServiceName, mockHostName, 1, true));
+ SASLServerMechanismRegistry registry;
+ auto swServer = registry.getServerMechanism("FAKE", "test");
+ ASSERT_NOT_OK(swServer.getStatus());
}
} // namespace
diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp
index f54fed2842a..d11ee7f1886 100644
--- a/src/mongo/db/auth/sasl_commands.cpp
+++ b/src/mongo/db/auth/sasl_commands.cpp
@@ -30,6 +30,7 @@
#include "mongo/platform/basic.h"
+#include <memory>
#include "mongo/base/init.h"
#include "mongo/base/status.h"
@@ -39,10 +40,10 @@
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/audit.h"
+#include "mongo/db/auth/authentication_session.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/authz_manager_external_state_mock.h"
#include "mongo/db/auth/authz_session_external_state_mock.h"
-#include "mongo/db/auth/sasl_authentication_session.h"
#include "mongo/db/auth/sasl_options.h"
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
@@ -117,12 +118,12 @@ public:
CmdSaslStart cmdSaslStart;
CmdSaslContinue cmdSaslContinue;
-Status buildResponse(const SaslAuthenticationSession* session,
+Status buildResponse(const AuthenticationSession* session,
const std::string& responsePayload,
BSONType responsePayloadType,
BSONObjBuilder* result) {
- result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId());
- result->appendBool(saslCommandDoneFieldName, session->isDone());
+ result->appendIntOrLL(saslCommandConversationIdFieldName, 1);
+ result->appendBool(saslCommandDoneFieldName, session->getMechanism().isDone());
if (responsePayload.size() > size_t(std::numeric_limits<int>::max())) {
return Status(ErrorCodes::InvalidLength, "Response payload too long");
@@ -159,96 +160,98 @@ Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) {
return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism);
}
-Status doSaslStep(const Client* client,
- SaslAuthenticationSession* session,
+Status doSaslStep(OperationContext* opCtx,
+ AuthenticationSession* session,
const BSONObj& cmdObj,
BSONObjBuilder* result) {
std::string payload;
BSONType type = EOO;
Status status = saslExtractPayload(cmdObj, &payload, &type);
- if (!status.isOK())
+ if (!status.isOK()) {
return status;
+ }
+
+ auto& mechanism = session->getMechanism();
- std::string responsePayload;
// Passing in a payload and extracting a responsePayload
- status = session->step(payload, &responsePayload);
+ StatusWith<std::string> swResponse = mechanism.step(opCtx, payload);
- if (!status.isOK()) {
- log() << session->getMechanism() << " authentication failed for "
- << session->getPrincipalId() << " on " << session->getAuthenticationDatabase()
- << " from client " << client->getRemote().toString() << " ; " << redact(status);
+ if (!swResponse.isOK()) {
+ log() << "SASL " << mechanism.mechanismName() << " authentication failed for "
+ << mechanism.getPrincipalName() << " on " << mechanism.getAuthenticationDatabase()
+ << " from client " << opCtx->getClient()->getRemote().toString() << " ; "
+ << redact(swResponse.getStatus());
sleepmillis(saslGlobalParams.authFailedDelay.load());
// All the client needs to know is that authentication has failed.
return AuthorizationManager::authenticationFailedStatus;
}
- status = buildResponse(session, responsePayload, type, result);
- if (!status.isOK())
+ status = buildResponse(session, swResponse.getValue(), type, result);
+ if (!status.isOK()) {
return status;
+ }
- if (session->isDone()) {
- UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase());
+ if (mechanism.isDone()) {
+ UserName userName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase());
status =
- session->getAuthorizationSession()->addAndAuthorizeUser(session->getOpCtxt(), userName);
+ AuthorizationSession::get(opCtx->getClient())->addAndAuthorizeUser(opCtx, userName);
if (!status.isOK()) {
return status;
}
if (!serverGlobalParams.quiet.load()) {
- log() << "Successfully authenticated as principal " << session->getPrincipalId()
- << " on " << session->getAuthenticationDatabase();
+ log() << "Successfully authenticated as principal " << mechanism.getPrincipalName()
+ << " on " << mechanism.getAuthenticationDatabase();
}
}
return Status::OK();
}
-Status doSaslStart(const Client* client,
- SaslAuthenticationSession* session,
- const std::string& db,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
+StatusWith<std::unique_ptr<AuthenticationSession>> doSaslStart(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder* result) {
bool autoAuthorize = false;
Status status = bsonExtractBooleanFieldWithDefault(
cmdObj, saslCommandAutoAuthorizeFieldName, autoAuthorizeDefault, &autoAuthorize);
if (!status.isOK())
return status;
- std::string mechanism;
- status = extractMechanism(cmdObj, &mechanism);
+ std::string mechanismName;
+ status = extractMechanism(cmdObj, &mechanismName);
if (!status.isOK())
return status;
- if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mechanism) &&
- mechanism != "SCRAM-SHA-1") {
- // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to
- // handle internal user authentication, SERVER-16534
- result->append(saslCommandMechanismListFieldName,
- saslGlobalParams.authenticationMechanisms);
- return Status(ErrorCodes::BadValue,
- mongoutils::str::stream() << "Unsupported mechanism " << mechanism);
+ StatusWith<std::unique_ptr<ServerMechanismBase>> swMech =
+ SASLServerMechanismRegistry::get(opCtx->getServiceContext())
+ .getServerMechanism(mechanismName, db);
+
+ if (!swMech.isOK()) {
+ return swMech.getStatus();
}
- status = session->start(
- db, mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, autoAuthorize);
- if (!status.isOK())
- return status;
+ auto session = std::make_unique<AuthenticationSession>(std::move(swMech.getValue()));
+ Status statusStep = doSaslStep(opCtx, session.get(), cmdObj, result);
+ if (!statusStep.isOK()) {
+ return statusStep;
+ }
- return doSaslStep(client, session, cmdObj, result);
+ return std::move(session);
}
-Status doSaslContinue(const Client* client,
- SaslAuthenticationSession* session,
+Status doSaslContinue(OperationContext* opCtx,
+ AuthenticationSession* session,
const BSONObj& cmdObj,
BSONObjBuilder* result) {
int64_t conversationId = 0;
Status status = extractConversationId(cmdObj, &conversationId);
if (!status.isOK())
return status;
- if (conversationId != session->getConversationId())
+ if (conversationId != 1)
return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id");
- return doSaslStep(client, session, cmdObj, result);
+ return doSaslStep(opCtx, session, cmdObj, result);
}
CmdSaslStart::CmdSaslStart() : BasicCommand(saslStartCommandName) {}
@@ -269,33 +272,32 @@ bool CmdSaslStart::run(OperationContext* opCtx,
const std::string& db,
const BSONObj& cmdObj,
BSONObjBuilder& result) {
- Client* client = Client::getCurrent();
+ Client* client = opCtx->getClient();
AuthenticationSession::set(client, std::unique_ptr<AuthenticationSession>());
- std::string mechanism;
- if (!extractMechanism(cmdObj, &mechanism).isOK()) {
+ std::string mechanismName;
+ if (!extractMechanism(cmdObj, &mechanismName).isOK()) {
return false;
}
- SaslAuthenticationSession* session =
- SaslAuthenticationSession::create(AuthorizationSession::get(client), db, mechanism);
-
- std::unique_ptr<AuthenticationSession> sessionGuard(session);
-
- session->setOpCtxt(opCtx);
-
- Status status = doSaslStart(client, session, db, cmdObj, &result);
- CommandHelpers::appendCommandStatus(result, status);
+ StatusWith<std::unique_ptr<AuthenticationSession>> swSession =
+ doSaslStart(opCtx, db, cmdObj, &result);
+ CommandHelpers::appendCommandStatus(result, swSession.getStatus());
+ if (!swSession.isOK()) {
+ return false;
+ }
+ auto session = std::move(swSession.getValue());
- if (session->isDone()) {
+ auto& mechanism = session->getMechanism();
+ if (mechanism.isDone()) {
audit::logAuthentication(client,
- session->getMechanism(),
- UserName(session->getPrincipalId(), db),
- status.code());
+ mechanismName,
+ UserName(mechanism.getPrincipalName(), db),
+ swSession.getStatus().code());
} else {
- AuthenticationSession::swap(client, sessionGuard);
+ AuthenticationSession::swap(client, session);
}
- return status.isOK();
+ return swSession.isOK();
}
CmdSaslContinue::CmdSaslContinue() : BasicCommand(saslContinueCommandName) {}
@@ -313,33 +315,32 @@ bool CmdSaslContinue::run(OperationContext* opCtx,
std::unique_ptr<AuthenticationSession> sessionGuard;
AuthenticationSession::swap(client, sessionGuard);
- if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) {
+ if (!sessionGuard) {
return CommandHelpers::appendCommandStatus(
result, Status(ErrorCodes::ProtocolError, "No SASL session state found"));
}
- SaslAuthenticationSession* session =
- static_cast<SaslAuthenticationSession*>(sessionGuard.get());
+ AuthenticationSession* session = static_cast<AuthenticationSession*>(sessionGuard.get());
+ auto& mechanism = session->getMechanism();
// Authenticating the __system@local user to the admin database on mongos is required
// by the auth passthrough test suite.
- if (session->getAuthenticationDatabase() != db && !Command::testCommandsEnabled) {
+ if (mechanism.getAuthenticationDatabase() != db && !Command::testCommandsEnabled) {
return CommandHelpers::appendCommandStatus(
result,
Status(ErrorCodes::ProtocolError,
"Attempt to switch database target during SASL authentication."));
}
- session->setOpCtxt(opCtx);
-
- Status status = doSaslContinue(client, session, cmdObj, &result);
+ Status status = doSaslContinue(opCtx, session, cmdObj, &result);
CommandHelpers::appendCommandStatus(result, status);
- if (session->isDone()) {
- audit::logAuthentication(client,
- session->getMechanism(),
- UserName(session->getPrincipalId(), db),
- status.code());
+ if (mechanism.isDone()) {
+ audit::logAuthentication(
+ client,
+ mechanism.mechanismName(),
+ UserName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase()),
+ status.code());
} else {
AuthenticationSession::swap(client, sessionGuard);
}
@@ -348,7 +349,7 @@ bool CmdSaslContinue::run(OperationContext* opCtx,
}
// The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands
-MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, ("NativeSaslServerCore"))
+MONGO_INITIALIZER(PreSaslCommands)
(InitializerContext*) {
if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509"))
CmdAuthenticate::disableAuthMechanism("MONGODB-X509");
diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp
deleted file mode 100644
index 24ffc0eef08..00000000000
--- a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-/**
- * 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 <http://www.gnu.org/licenses/>.
- *
- * 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"
-#include "mongo/util/icu.h"
-#include "mongo/util/scopeguard.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;
- }
-}
-
-/**
- * Fetch credentials for the named user eithing using the unnormalized form provided,
- * or the form returned from saslPrep().
- * If both forms exist as different user records, produce an error.
- */
-User::CredentialData getUserCredentials(OperationContext* opCtx, const std::string& username) {
- AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext());
- User* rawObj = nullptr;
- User* prepObj = nullptr;
- auto guard = MakeGuard([authManager, &rawObj, &prepObj] {
- if (prepObj) {
- authManager->releaseUser(prepObj);
- }
- if (rawObj) {
- authManager->releaseUser(rawObj);
- }
- });
-
- const auto rawUserName = uassertStatusOK(UserName::parse(username));
- const auto rawStatus = authManager->acquireUser(opCtx, rawUserName, &rawObj);
-
- // Attempt to normalize the provided username (excluding DB portion).
- // If saslPrep() fails, then there can't possibly be another user with
- // compatibility equivalence, so fall-through.
- const auto swPrepUser = saslPrep(rawUserName.getUser());
- if (swPrepUser.isOK()) {
- UserName prepUserName(swPrepUser.getValue(), rawUserName.getDB());
- if (prepUserName != rawUserName) {
- // User has a SASLPREPable name which differs from the raw presentation.
- // Double check that we don't have a different user by that new name.
- const auto prepStatus = authManager->acquireUser(opCtx, prepUserName, &prepObj);
- if (prepStatus.isOK()) {
- // If both statuses are OK, then we have two distinct users with "different" names.
- uassert(ErrorCodes::BadValue,
- "Two users exist with names exhibiting compatibility equivalence",
- !rawStatus.isOK());
- // Otherwise, only the normalized version exists.
- return prepObj->getCredentials();
- }
- }
- }
-
- uassertStatusOK(rawStatus);
- return rawObj->getCredentials();
-}
-
-} // namespace
-
-
-void SASLMechanismAdvertiser::advertise(OperationContext* opCtx,
- const BSONObj& cmdObj,
- BSONObjBuilder* result) {
- BSONElement saslSupportedMechs = cmdObj["saslSupportedMechs"];
- if (saslSupportedMechs.type() == BSONType::String) {
- const auto credentials = getUserCredentials(opCtx, saslSupportedMechs.String());
-
- BSONArrayBuilder mechanismsBuilder;
- if (credentials.isExternal) {
- for (const StringData& userMechanism : {"GSSAPI", "PLAIN"}) {
- appendMechanismIfSupported(userMechanism, &mechanismsBuilder);
- }
- }
- if (credentials.scram<SHA1Block>().isValid()) {
- appendMechanismIfSupported("SCRAM-SHA-1", &mechanismsBuilder);
- }
- if (credentials.scram<SHA256Block>().isValid()) {
- appendMechanismIfSupported("SCRAM-SHA-256", &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
deleted file mode 100644
index cae0e65895d..00000000000
--- a/src/mongo/db/auth/sasl_mechanism_advertiser.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * 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 <http://www.gnu.org/licenses/>.
- *
- * 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/native_sasl_authentication_session.h b/src/mongo/db/auth/sasl_mechanism_policies.h
index 3688a4aad31..bdb6b98d48d 100644
--- a/src/mongo/db/auth/native_sasl_authentication_session.h
+++ b/src/mongo/db/auth/sasl_mechanism_policies.h
@@ -1,5 +1,5 @@
-/*
- * Copyright (C) 2014 10gen, Inc.
+/**
+ * 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,
@@ -28,43 +28,51 @@
#pragma once
-#include <cstdint>
-#include <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
-#include "mongo/db/auth/authentication_session.h"
-#include "mongo/db/auth/sasl_authentication_session.h"
-#include "mongo/db/auth/sasl_server_conversation.h"
+#include "mongo/crypto/sha_block.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
namespace mongo {
-/**
- * Authentication session data for the server side of SASL authentication.
- */
-class NativeSaslAuthenticationSession : public SaslAuthenticationSession {
- MONGO_DISALLOW_COPYING(NativeSaslAuthenticationSession);
-
-public:
- explicit NativeSaslAuthenticationSession(AuthorizationSession* authSession);
- virtual ~NativeSaslAuthenticationSession();
+struct PLAINPolicy {
+ static constexpr StringData getName() {
+ return "PLAIN"_sd;
+ }
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{};
+ }
+};
- virtual Status start(StringData authenticationDatabase,
- StringData mechanism,
- StringData serviceName,
- StringData serviceHostname,
- int64_t conversationId,
- bool autoAuthorize);
+struct SCRAMSHA1Policy {
+ using HashBlock = SHA1Block;
- virtual Status step(StringData inputData, std::string* outputData);
+ static constexpr StringData getName() {
+ return "SCRAM-SHA-1"_sd;
+ }
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
+ }
+};
- virtual std::string getPrincipalId() const;
+struct SCRAMSHA256Policy {
+ using HashBlock = SHA256Block;
- virtual const char* getMechanism() const;
+ static constexpr StringData getName() {
+ return "SCRAM-SHA-256"_sd;
+ }
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
+ }
+};
-private:
- std::string _mechanism;
- std::unique_ptr<SaslServerConversation> _saslConversation;
+struct GSSAPIPolicy {
+ static constexpr StringData getName() {
+ return "GSSAPI"_sd;
+ }
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
+ }
};
+
+
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_mechanism_registry.cpp b/src/mongo/db/auth/sasl_mechanism_registry.cpp
new file mode 100644
index 00000000000..b46ba9978f3
--- /dev/null
+++ b/src/mongo/db/auth/sasl_mechanism_registry.cpp
@@ -0,0 +1,179 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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_registry.h"
+
+#include "mongo/base/init.h"
+#include "mongo/db/auth/sasl_options.h"
+#include "mongo/db/auth/user.h"
+#include "mongo/util/icu.h"
+#include "mongo/util/net/sock.h"
+#include "mongo/util/scopeguard.h"
+#include "mongo/util/sequence_util.h"
+
+namespace mongo {
+
+namespace {
+const auto getSASLServerMechanismRegistry =
+ ServiceContext::declareDecoration<std::unique_ptr<SASLServerMechanismRegistry>>();
+
+/**
+ * Fetch credentials for the named user either using the unnormalized form provided,
+ * or the form returned from saslPrep().
+ * If both forms exist as different user records, produce an error.
+ */
+User* getUserPtr(OperationContext* opCtx, const UserName& userName) {
+ AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext());
+
+ User* rawObj = nullptr;
+ const auto rawStatus = authManager->acquireUser(opCtx, userName, &rawObj);
+ auto rawGuard = MakeGuard([authManager, &rawObj] {
+ if (rawObj) {
+ authManager->releaseUser(rawObj);
+ }
+ });
+
+ // Attempt to normalize the provided username (excluding DB portion).
+ // If saslPrep() fails, then there can't possibly be another user with
+ // compatibility equivalence, so fall-through.
+ const auto swPrepUser = saslPrep(userName.getUser());
+ if (swPrepUser.isOK()) {
+ UserName prepUserName(swPrepUser.getValue(), userName.getDB());
+ if (prepUserName != userName) {
+ // User has a SASLPREPable name which differs from the raw presentation.
+ // Double check that we don't have a different user by that new name.
+ User* prepObj = nullptr;
+ const auto prepStatus = authManager->acquireUser(opCtx, prepUserName, &prepObj);
+
+ auto prepGuard = MakeGuard([authManager, &prepObj] {
+ if (prepObj) {
+ authManager->releaseUser(prepObj);
+ }
+ });
+
+ if (prepStatus.isOK()) {
+ // If both statuses are OK, then we have two distinct users with "different" names.
+ uassert(ErrorCodes::BadValue,
+ "Two users exist with names exhibiting compatibility equivalence",
+ !rawStatus.isOK());
+ // Otherwise, only the normalized version exists.
+ User* returnObj = nullptr;
+ std::swap(returnObj, prepObj);
+ return returnObj;
+ }
+ }
+ }
+
+ uassertStatusOK(rawStatus);
+ User* returnObj = nullptr;
+ std::swap(returnObj, rawObj);
+ return returnObj;
+}
+
+} // namespace
+
+SASLServerMechanismRegistry& SASLServerMechanismRegistry::get(ServiceContext* serviceContext) {
+ auto& uptr = getSASLServerMechanismRegistry(serviceContext);
+ invariant(uptr);
+ return *uptr;
+}
+
+void SASLServerMechanismRegistry::set(ServiceContext* service,
+ std::unique_ptr<SASLServerMechanismRegistry> registry) {
+ getSASLServerMechanismRegistry(service) = std::move(registry);
+}
+
+StatusWith<std::unique_ptr<ServerMechanismBase>> SASLServerMechanismRegistry::getServerMechanism(
+ StringData mechanismName, std::string authenticationDatabase) {
+ auto& map = _getMapRef(authenticationDatabase);
+
+ auto it = map.find(mechanismName.toString());
+ if (it != map.end()) {
+ return it->second->create(std::move(authenticationDatabase));
+ }
+
+ return Status(ErrorCodes::BadValue,
+ mongoutils::str::stream() << "Unsupported mechanism '" << mechanismName
+ << "' on authentication database '"
+ << authenticationDatabase
+ << "'");
+}
+
+void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContext* opCtx,
+ const BSONObj& isMasterCmd,
+ BSONObjBuilder* builder) {
+ BSONElement saslSupportedMechs = isMasterCmd["saslSupportedMechs"];
+ if (saslSupportedMechs.type() == BSONType::String) {
+
+ const auto userName = uassertStatusOK(UserName::parse(saslSupportedMechs.String()));
+ const auto userPtr = getUserPtr(opCtx, userName);
+ auto guard = MakeGuard([&opCtx, &userPtr] {
+ AuthorizationManager* authManager =
+ AuthorizationManager::get(opCtx->getServiceContext());
+ authManager->releaseUser(userPtr);
+ });
+
+ BSONArrayBuilder mechanismsBuilder;
+ auto& map = _getMapRef(userName.getDB());
+
+ for (const auto& factoryIt : map) {
+ SecurityPropertySet properties = factoryIt.second->properties();
+ if (!properties.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText,
+ SecurityProperty::kMutualAuth}) &&
+ userName.getDB() != "$external") {
+ continue;
+ }
+
+ if (factoryIt.second->canMakeMechanismForUser(userPtr)) {
+ mechanismsBuilder << factoryIt.first;
+ }
+ }
+
+ builder->appendArray("saslSupportedMechs", mechanismsBuilder.arr());
+ }
+}
+
+bool SASLServerMechanismRegistry::_mechanismSupportedByConfig(StringData mechName) {
+ return sequenceContains(saslGlobalParams.authenticationMechanisms, mechName);
+}
+
+MONGO_INITIALIZER_WITH_PREREQUISITES(CreateSASLServerMechanismRegistry, ("SetGlobalEnvironment"))
+(::mongo::InitializerContext* context) {
+ if (saslGlobalParams.hostName.empty())
+ saslGlobalParams.hostName = getHostNameCached();
+ if (saslGlobalParams.serviceName.empty())
+ saslGlobalParams.serviceName = "mongodb";
+
+ auto registry = stdx::make_unique<SASLServerMechanismRegistry>();
+ SASLServerMechanismRegistry::set(getGlobalServiceContext(), std::move(registry));
+ return Status::OK();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h
new file mode 100644
index 00000000000..6a6d8fe9d33
--- /dev/null
+++ b/src/mongo/db/auth/sasl_mechanism_registry.h
@@ -0,0 +1,336 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <memory>
+#include <unordered_map>
+
+#include "mongo/base/string_data.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/service_context.h"
+
+namespace mongo {
+
+class User;
+
+/**
+ * The set of attributes SASL mechanisms may possess.
+ * Different SASL mechanisms provide different types of assurances to clients and servers.
+ * These SecurityProperties are attached to mechanism types to allow reasoning about them.
+ *
+ * Descriptions of individual properties assumed while using a mechanism with the property.
+ */
+enum class SecurityProperty : size_t {
+ kMutualAuth, // Both clients and servers are assured of the other's identity.
+ kNoPlainText, // Messages sent across the wire don't include the plaintext password.
+ kLastSecurityProperty, // Do not use. An enum value for internal bookkeeping.
+};
+
+/** Allows a set of SecurityProperties to be defined in an initializer list. */
+using SecurityPropertyList = std::initializer_list<SecurityProperty>;
+
+/** A set of SecurityProperties which may be exhibited by a SASL mechanism. */
+class SecurityPropertySet {
+public:
+ explicit SecurityPropertySet(SecurityPropertyList props) {
+ for (auto prop : props) {
+ _set.set(static_cast<size_t>(prop));
+ }
+ }
+
+ /** Returns true if the set contains all of the requested properties. */
+ bool hasAllProperties(SecurityPropertySet propertySet) const {
+ for (size_t i = 0; i < propertySet._set.size(); ++i) {
+ if (propertySet._set[i] && !_set[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+private:
+ std::bitset<static_cast<size_t>(SecurityProperty::kLastSecurityProperty)> _set;
+};
+
+/**
+ * SASL server mechanisms are made by a corresponding factory.
+ * Mechanisms have properties. We wish to be able to manipulate these properties both at runtime
+ * and compile-time. These properties should apply to, and be accessible from, mechanisms and
+ * factories. Client mechanisms/factories should be able to use the same property definitions.
+ * This allows safe mechanism negotiation.
+ *
+ * The properties are set by creating a Policy class, and making mechanisms inherit from a helper
+ * derived from the class. Factories are derived from a helper which uses the mechanism.
+ */
+
+/** Exposes properties of the SASL mechanism. */
+class SaslServerCommonBase {
+public:
+ virtual ~SaslServerCommonBase() = default;
+ virtual StringData mechanismName() const = 0;
+ virtual SecurityPropertySet properties() const = 0;
+};
+
+/**
+ * Base class shared by all server-side SASL mechanisms.
+ */
+class ServerMechanismBase : public SaslServerCommonBase {
+public:
+ explicit ServerMechanismBase(std::string authenticationDatabase)
+ : _authenticationDatabase(std::move(authenticationDatabase)) {}
+
+ virtual ~ServerMechanismBase() = default;
+
+ /**
+ * Returns the principal name which this mechanism is performing authentication for.
+ * This name is provided by the client, in the SASL messages they send the server.
+ * This value may not be available until after some number of conversation steps have
+ * occurred.
+ *
+ * This method is virtual so more complex implementations can obtain this value from a
+ * non-member.
+ */
+ virtual StringData getPrincipalName() const {
+ return _principalName;
+ }
+
+ /**
+ * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser."
+ *
+ * The standard rule in MongoDB is simple. The authenticated user name must be the same as the
+ * requested user name.
+ */
+ virtual bool isAuthorizedToActAs(StringData requestedUser, StringData authenticatedUser) {
+ return requestedUser == authenticatedUser;
+ }
+
+ /**
+ * Performs a single step of a SASL exchange. Takes an input provided by a client,
+ * and either returns an error, or a response to be sent back.
+ */
+ StatusWith<std::string> step(OperationContext* opCtx, StringData input) {
+ auto result = stepImpl(opCtx, input);
+ if (result.isOK()) {
+ bool isDone;
+ std::string responseMessage;
+ std::tie(isDone, responseMessage) = result.getValue();
+
+ _done = isDone;
+ return responseMessage;
+ }
+ return result.getStatus();
+ }
+
+ /**
+ * Returns true if the conversation has completed.
+ * Note that this does not mean authentication succeeded!
+ * An error may have occurred.
+ */
+ bool isDone() const {
+ return _done;
+ }
+
+ /** Returns which database contains the user which authentication is being performed against. */
+ StringData getAuthenticationDatabase() const {
+ if (Command::testCommandsEnabled && _authenticationDatabase == "admin" &&
+ getPrincipalName() == internalSecurity.user->getName().getUser()) {
+ // Allows authenticating as the internal user against the admin database. This is to
+ // support the auth passthrough test framework on mongos (since you can't use the local
+ // database on a mongos, so you can't auth as the internal user without this).
+ return internalSecurity.user->getName().getDB();
+ } else {
+ return _authenticationDatabase;
+ }
+ }
+
+protected:
+ /**
+ * Mechanism provided step implementation.
+ * On failure, returns a non-OK status. On success, returns a tuple consisting of
+ * a boolean indicating whether the mechanism has completed, and the string
+ * containing the server's response to the client.
+ */
+ virtual StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
+ StringData input) = 0;
+
+ bool _done = false;
+ std::string _principalName;
+ std::string _authenticationDatabase;
+};
+
+/** Base class for server mechanism factories. */
+class ServerFactoryBase : public SaslServerCommonBase {
+public:
+ /**
+ * Returns if the factory is capable of producing a server mechanism object which could
+ * authenticate the provided user.
+ */
+ virtual bool canMakeMechanismForUser(const User* user) const = 0;
+
+ /** Produces a unique_ptr containing a server SASL mechanism.*/
+ std::unique_ptr<ServerMechanismBase> create(std::string authenticationDatabase) {
+ std::unique_ptr<ServerMechanismBase> rv(
+ this->createImpl(std::move(authenticationDatabase)));
+ invariant(rv->mechanismName() == this->mechanismName());
+ return rv;
+ }
+
+private:
+ virtual ServerMechanismBase* createImpl(std::string authenticationDatabase) = 0;
+};
+
+/** Instantiates a class which provides runtime access to Policy properties. */
+template <typename Policy>
+class MakeServerMechanism : public ServerMechanismBase {
+public:
+ explicit MakeServerMechanism(std::string authenticationDatabase)
+ : ServerMechanismBase(std::move(authenticationDatabase)) {}
+ virtual ~MakeServerMechanism() = default;
+
+ using policy_type = Policy;
+
+ StringData mechanismName() const final {
+ return policy_type::getName();
+ }
+
+ SecurityPropertySet properties() const final {
+ return policy_type::getProperties();
+ }
+};
+
+/** Instantiates a class which provides runtime access to Policy properties. */
+template <typename ServerMechanism>
+class MakeServerFactory : public ServerFactoryBase {
+public:
+ static_assert(std::is_base_of<MakeServerMechanism<typename ServerMechanism::policy_type>,
+ ServerMechanism>::value,
+ "MakeServerFactory must be instantiated with a ServerMechanism derived from "
+ "MakeServerMechanism");
+
+ using mechanism_type = ServerMechanism;
+ using policy_type = typename ServerMechanism::policy_type;
+
+ virtual ServerMechanism* createImpl(std::string authenticationDatabase) override {
+ return new ServerMechanism(std::move(authenticationDatabase));
+ }
+
+ StringData mechanismName() const final {
+ return policy_type::getName();
+ }
+
+ SecurityPropertySet properties() const final {
+ return policy_type::getProperties();
+ }
+};
+
+/**
+ * Tracks server-side SASL mechanisms. Mechanisms' factories are registered with this class during
+ * server initialization. During authentication, this class finds a factory, to obtains a
+ * mechanism from. Also capable of producing a list of mechanisms which would be valid for a
+ * particular user.
+ */
+class SASLServerMechanismRegistry {
+public:
+ static SASLServerMechanismRegistry& get(ServiceContext* serviceContext);
+ static void set(ServiceContext* service, std::unique_ptr<SASLServerMechanismRegistry> registry);
+
+ /**
+ * Produces a list of SASL mechanisms which can be used to authenticate as a user.
+ * If isMasterCmd contains a field with a username called 'saslSupportedMechs',
+ * will populate 'builder' with an Array called saslSupportedMechs containing each mechanism the
+ * user supports.
+ */
+ void advertiseMechanismNamesForUser(OperationContext* opCtx,
+ const BSONObj& isMasterCmd,
+ BSONObjBuilder* builder);
+
+ /**
+ * Gets a mechanism object which corresponds to the provided name.
+ * The mechanism will be able to authenticate users which exist on the
+ * "authenticationDatabase".
+ */
+ StatusWith<std::unique_ptr<ServerMechanismBase>> getServerMechanism(
+ StringData mechanismName, std::string authenticationDatabase);
+
+ /**
+ * Registers a factory T to produce a type of SASL mechanism.
+ * If 'internal' is false, the factory will be used to create mechanisms for authentication
+ * attempts on $external. Otherwise, the mechanism may be used for any database but $external.
+ * This allows distinct mechanisms with the same name for the servers' different authentication
+ * domains.
+ */
+ enum ValidateGlobalMechanisms : bool {
+ kValidateGlobalMechanisms = true,
+ kNoValidateGlobalMechanisms = false
+ };
+ template <typename T>
+ bool registerFactory(
+ ValidateGlobalMechanisms validateGlobalConfig = kValidateGlobalMechanisms) {
+ using policy_type = typename T::policy_type;
+ auto mechName = policy_type::getName();
+
+ // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to
+ // handle internal user authentication, SERVER-16534
+ if (validateGlobalConfig &&
+ (mechName != "SCRAM-SHA-1" && !_mechanismSupportedByConfig(mechName))) {
+ return false;
+ }
+
+ invariant(_getMapRef(T::mechanism_type::isInternal)
+ .emplace(mechName.toString(), std::make_unique<T>())
+ .second);
+ return true;
+ }
+
+private:
+ stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>>& _getMapRef(
+ StringData dbName) {
+ return _getMapRef(dbName != "$external"_sd);
+ }
+
+ stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>>& _getMapRef(
+ bool internal) {
+ if (internal) {
+ return _internalMap;
+ }
+ return _externalMap;
+ }
+
+ bool _mechanismSupportedByConfig(StringData mechName);
+
+ // Stores factories which make mechanisms for all databases other than $external
+ stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>> _internalMap;
+ // Stores factories which make mechanisms exclusively for $external
+ stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>> _externalMap;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp
new file mode 100644
index 00000000000..8c8515adb43
--- /dev/null
+++ b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp
@@ -0,0 +1,350 @@
+/**
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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/db/auth/sasl_mechanism_registry.h"
+#include "mongo/crypto/mechanism_scram.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/authz_manager_external_state_mock.h"
+#include "mongo/db/operation_context_noop.h"
+#include "mongo/db/service_context_noop.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+
+TEST(SecurityProperty, emptyHasEmptyProperties) {
+ SecurityPropertySet set(SecurityPropertySet{});
+ ASSERT_TRUE(set.hasAllProperties(set));
+ ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth}));
+ ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText}));
+}
+
+TEST(SecurityProperty, mutualHasMutalAndEmptyProperties) {
+ SecurityPropertySet set(SecurityPropertySet{SecurityProperty::kMutualAuth});
+ ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{}));
+ ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth}));
+ ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText}));
+}
+
+TEST(SecurityProperty, mutualAndPlainHasAllSubsets) {
+ SecurityPropertySet set{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText};
+ ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{}));
+ ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth}));
+ ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText}));
+ ASSERT_TRUE(set.hasAllProperties(
+ SecurityPropertySet{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText}));
+}
+
+// Policy for a hypothetical "FOO" SASL mechanism.
+struct FooPolicy {
+ static constexpr StringData getName() {
+ return "FOO"_sd;
+ }
+
+ // This mech is kind of dangerous, it sends plaintext passwords across the wire.
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{SecurityProperty::kMutualAuth};
+ }
+};
+
+template <bool argIsInternal>
+class FooMechanism : public MakeServerMechanism<FooPolicy> {
+public:
+ static const bool isInternal = argIsInternal;
+ explicit FooMechanism(std::string authenticationDatabase)
+ : MakeServerMechanism<FooPolicy>(std::move(authenticationDatabase)) {}
+
+protected:
+ StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
+ StringData input) final {
+ return std::make_tuple(true, std::string());
+ }
+};
+
+template <bool argIsInternal>
+class FooMechanismFactory : public MakeServerFactory<FooMechanism<argIsInternal>> {
+public:
+ bool canMakeMechanismForUser(const User* user) const final {
+ return true;
+ }
+};
+
+// Policy for a hypothetical "BAR" SASL mechanism.
+struct BarPolicy {
+ static constexpr StringData getName() {
+ return "BAR"_sd;
+ }
+
+ static SecurityPropertySet getProperties() {
+ return SecurityPropertySet{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText};
+ }
+};
+
+template <bool argIsInternal>
+class BarMechanism : public MakeServerMechanism<BarPolicy> {
+public:
+ static const bool isInternal = argIsInternal;
+ explicit BarMechanism(std::string authenticationDatabase)
+ : MakeServerMechanism<BarPolicy>(std::move(authenticationDatabase)) {}
+
+protected:
+ StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
+ StringData input) final {
+ return std::make_tuple(true, std::string());
+ }
+};
+
+template <bool argIsInternal>
+class BarMechanismFactory : public MakeServerFactory<BarMechanism<argIsInternal>> {
+public:
+ bool canMakeMechanismForUser(const User* user) const final {
+ return true;
+ }
+};
+
+
+class MechanismRegistryTest : public mongo::unittest::Test {
+public:
+ MechanismRegistryTest()
+ : opClient(serviceContext.makeClient("mechanismRegistryTest")),
+ opCtx(serviceContext.makeOperationContext(opClient.get())),
+ authManagerExternalState(new AuthzManagerExternalStateMock()),
+ authManager(new AuthorizationManager(
+ std::unique_ptr<AuthzManagerExternalStateMock>(authManagerExternalState))) {
+ AuthorizationManager::set(&serviceContext,
+ std::unique_ptr<AuthorizationManager>(authManager));
+
+ ASSERT_OK(authManagerExternalState->updateOne(
+ opCtx.get(),
+ AuthorizationManager::versionCollectionNamespace,
+ AuthorizationManager::versionDocumentQuery,
+ BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName
+ << AuthorizationManager::schemaVersion26Final)),
+ true,
+ BSONObj()));
+
+ ASSERT_OK(authManagerExternalState->insert(
+ opCtx.get(),
+ NamespaceString("admin.system.users"),
+ BSON("_id"
+ << "test.sajack"
+ << "user"
+ << "sajack"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("SCRAM-SHA-256"
+ << scram::Secrets<SHA256Block>::generateCredentials("sajack‍", 15000))
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+
+
+ ASSERT_OK(authManagerExternalState->insert(opCtx.get(),
+ NamespaceString("admin.system.users"),
+ BSON("_id"
+ << "$external.sajack"
+ << "user"
+ << "sajack"
+ << "db"
+ << "$external"
+ << "credentials"
+ << BSON("external" << true)
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+
+
+ ASSERT_OK(authManagerExternalState->insert(
+ opCtx.get(),
+ NamespaceString("admin.system.users"),
+ BSON("_id"
+ << "test.collision"
+ << "user"
+ << "collision"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("SCRAM-SHA-256"
+ << scram::Secrets<SHA256Block>::generateCredentials("collision", 15000))
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+
+ // A user whose name does not equal "test.collision"'s, but ends in a Zero Width Joiner.
+ ASSERT_OK(authManagerExternalState->insert(
+ opCtx.get(),
+ NamespaceString("admin.system.users"),
+ BSON("_id"
+ << "test.collision‍" // This string ends in a ZWJ
+ << "user"
+ << "collision‍" // This string ends in a ZWJ
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("SCRAM-SHA-256"
+ << scram::Secrets<SHA256Block>::generateCredentials("collision", 15000))
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ }
+
+ ServiceContextNoop serviceContext;
+ ServiceContext::UniqueClient opClient;
+ ServiceContext::UniqueOperationContext opCtx;
+ AuthzManagerExternalStateMock* authManagerExternalState;
+ AuthorizationManager* authManager;
+
+ SASLServerMechanismRegistry registry;
+};
+
+TEST_F(MechanismRegistryTest, acquireInternalMechanism) {
+ registry.registerFactory<FooMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "test");
+ ASSERT_OK(swMechanism.getStatus());
+}
+
+TEST_F(MechanismRegistryTest, cantAcquireInternalMechanismOnExternal) {
+ registry.registerFactory<FooMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "$external");
+ ASSERT_NOT_OK(swMechanism.getStatus());
+}
+
+TEST_F(MechanismRegistryTest, acquireExternalMechanism) {
+ registry.registerFactory<FooMechanismFactory<false>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "$external");
+ ASSERT_OK(swMechanism.getStatus());
+}
+
+TEST_F(MechanismRegistryTest, cantAcquireExternalMechanismOnInternal) {
+ registry.registerFactory<FooMechanismFactory<false>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "test");
+ ASSERT_NOT_OK(swMechanism.getStatus());
+}
+
+
+TEST_F(MechanismRegistryTest, invalidUserCantAdvertiseMechs) {
+ registry.registerFactory<FooMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ BSONObjBuilder builder;
+
+ ASSERT_THROWS(
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "test.noSuchUser"),
+ &builder),
+ AssertionException);
+}
+
+TEST_F(MechanismRegistryTest, collisionsPreventAdvertisement) {
+ registry.registerFactory<FooMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ BSONObjBuilder builder;
+
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "test.collision"),
+ &builder);
+ ASSERT_THROWS(
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "test.collision‍"),
+ &builder),
+ AssertionException);
+}
+
+TEST_F(MechanismRegistryTest, strongMechCanAdvertise) {
+ registry.registerFactory<BarMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+ registry.registerFactory<BarMechanismFactory<false>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ BSONObjBuilder builder;
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "test.sajack"),
+ &builder);
+
+ BSONObj obj = builder.done();
+ ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("BAR")), obj);
+
+ BSONObjBuilder builderExternal;
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "$external.sajack"),
+ &builderExternal);
+
+ BSONObj objExternal = builderExternal.done();
+ ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("BAR")), objExternal);
+}
+
+TEST_F(MechanismRegistryTest, weakMechCannotAdvertiseOnInternal) {
+ registry.registerFactory<FooMechanismFactory<true>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ BSONObjBuilder builder;
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "test.sajack"),
+ &builder);
+
+
+ BSONObj obj = builder.done();
+
+ ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSONArray()), obj);
+}
+
+TEST_F(MechanismRegistryTest, weakMechCanAdvertiseOnExternal) {
+ registry.registerFactory<FooMechanismFactory<false>>(
+ SASLServerMechanismRegistry::kNoValidateGlobalMechanisms);
+
+ BSONObjBuilder builder;
+ registry.advertiseMechanismNamesForUser(opCtx.get(),
+ BSON("isMaster" << 1 << "saslSupportedMechs"
+ << "$external.sajack"),
+ &builder);
+
+ BSONObj obj = builder.done();
+
+ ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("FOO")), obj);
+}
+
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp
index a21686f4af0..3c32c9b7f58 100644
--- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp
+++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp
@@ -26,10 +26,18 @@
* it in the license file.
*/
+#include "mongo/platform/basic.h"
+
+#include <string>
+
#include "mongo/db/auth/sasl_plain_server_conversation.h"
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
#include "mongo/crypto/mechanism_scram.h"
-#include "mongo/db/auth/sasl_authentication_session.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
+#include "mongo/db/auth/user.h"
#include "mongo/util/base64.h"
#include "mongo/util/password_digest.h"
#include "mongo/util/text.h"
@@ -61,17 +69,15 @@ StatusWith<bool> trySCRAM(const User::CredentialData& credentials, StringData pw
}
} // namespace
-SaslPLAINServerConversation::SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession)
- : SaslServerConversation(saslAuthSession) {}
-
-SaslPLAINServerConversation::~SaslPLAINServerConversation(){};
-
-StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::string* outputData) {
- if (_saslAuthSession->getAuthenticationDatabase() == "$external") {
+StatusWith<std::tuple<bool, std::string>> SASLPlainServerMechanism::stepImpl(
+ OperationContext* opCtx, StringData inputData) {
+ if (_authenticationDatabase == "$external") {
return Status(ErrorCodes::AuthenticationFailed,
"PLAIN mechanism must be used with internal users");
}
+ AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext());
+
// Expecting user input on the form: [authz-id]\0authn-id\0pwd
std::string input = inputData.toString();
@@ -93,12 +99,14 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st
}
std::string authorizationIdentity = input.substr(0, firstNull);
- _user = input.substr(firstNull + 1, (secondNull - firstNull) - 1);
- if (_user.empty()) {
+ ServerMechanismBase::_principalName =
+ input.substr(firstNull + 1, (secondNull - firstNull) - 1);
+ if (ServerMechanismBase::_principalName.empty()) {
return Status(ErrorCodes::AuthenticationFailed,
str::stream()
<< "Incorrectly formatted PLAIN client message, empty username");
- } else if (!authorizationIdentity.empty() && authorizationIdentity != _user) {
+ } else if (!authorizationIdentity.empty() &&
+ authorizationIdentity != ServerMechanismBase::_principalName) {
return Status(ErrorCodes::AuthenticationFailed,
str::stream()
<< "SASL authorization identity must match authentication identity");
@@ -116,38 +124,45 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st
User* userObj;
// The authentication database is also the source database for the user.
- Status status =
- _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser(
- _saslAuthSession->getOpCtxt(),
- UserName(_user, _saslAuthSession->getAuthenticationDatabase()),
- &userObj);
+ Status status = authManager->acquireUser(
+ opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase), &userObj);
if (!status.isOK()) {
return status;
}
const auto creds = userObj->getCredentials();
- _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj);
+ authManager->releaseUser(userObj);
- *outputData = "";
const auto sha256Status = trySCRAM<SHA256Block>(creds, pwd->c_str());
if (!sha256Status.isOK()) {
- return sha256Status;
+ return sha256Status.getStatus();
}
if (sha256Status.getValue()) {
- return true;
+ return std::make_tuple(true, std::string());
}
- const auto authDigest = createPasswordDigest(_user, pwd->c_str());
+ const auto authDigest = createPasswordDigest(ServerMechanismBase::_principalName, pwd->c_str());
const auto sha1Status = trySCRAM<SHA1Block>(creds, authDigest);
if (!sha1Status.isOK()) {
- return sha1Status;
+ return sha1Status.getStatus();
}
if (sha1Status.getValue()) {
- return true;
+ return std::make_tuple(true, std::string());
}
return Status(ErrorCodes::AuthenticationFailed, str::stream() << "No credentials available.");
+
+
+ return std::make_tuple(true, std::string());
+}
+
+MONGO_INITIALIZER_WITH_PREREQUISITES(SASLPLAINServerMechanism,
+ ("CreateSASLServerMechanismRegistry"))
+(::mongo::InitializerContext* context) {
+ auto& registry = SASLServerMechanismRegistry::get(getGlobalServiceContext());
+ registry.registerFactory<PLAINServerFactory>();
+ return Status::OK();
}
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.h b/src/mongo/db/auth/sasl_plain_server_conversation.h
index 5d3b57ffa89..13d7003c65c 100644
--- a/src/mongo/db/auth/sasl_plain_server_conversation.h
+++ b/src/mongo/db/auth/sasl_plain_server_conversation.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 MongoDB Inc.
+ * 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,
@@ -28,30 +28,31 @@
#pragma once
-#include <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/db/auth/sasl_server_conversation.h"
+#include "mongo/db/auth/sasl_mechanism_policies.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
namespace mongo {
-/**
- * Server side authentication session for SASL PLAIN.
- */
-class SaslPLAINServerConversation : public SaslServerConversation {
- MONGO_DISALLOW_COPYING(SaslPLAINServerConversation);
+class SASLPlainServerMechanism : public MakeServerMechanism<PLAINPolicy> {
public:
- /**
- * Implements the server side of a SASL PLAIN mechanism session.
- *
- **/
- explicit SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession);
+ static const bool isInternal = true;
+
+ explicit SASLPlainServerMechanism(std::string authenticationDatabase)
+ : MakeServerMechanism<PLAINPolicy>(std::move(authenticationDatabase)) {}
- virtual ~SaslPLAINServerConversation();
+private:
+ StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
+ StringData input) final;
+};
- virtual StatusWith<bool> step(StringData inputData, std::string* outputData);
+class PLAINServerFactory : public MakeServerFactory<SASLPlainServerMechanism> {
+public:
+ bool canMakeMechanismForUser(const User* user) const final {
+ auto credentials = user->getCredentials();
+ return !credentials.isExternal && (credentials.scram<SHA1Block>().isValid() ||
+ credentials.scram<SHA256Block>().isValid());
+ }
};
+
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp
index 3e5fc0676b6..65720d6d383 100644
--- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp
+++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp
@@ -35,7 +35,14 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
+#include "mongo/base/disallow_copying.h"
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
+#include "mongo/crypto/mechanism_scram.h"
#include "mongo/crypto/sha1_block.h"
+#include "mongo/db/auth/sasl_mechanism_policies.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/auth/sasl_options.h"
#include "mongo/platform/random.h"
#include "mongo/util/base64.h"
@@ -46,26 +53,25 @@
namespace mongo {
-using std::unique_ptr;
-using std::string;
-StatusWith<bool> SaslSCRAMServerConversation::step(StringData inputData, std::string* outputData) {
+template <typename Policy>
+StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::stepImpl(
+ OperationContext* opCtx, StringData inputData) {
_step++;
if (_step > 3 || _step <= 0) {
return Status(ErrorCodes::AuthenticationFailed,
str::stream() << "Invalid SCRAM authentication step: " << _step);
}
+
if (_step == 1) {
- return _firstStep(inputData, outputData);
+ return _firstStep(opCtx, inputData);
}
if (_step == 2) {
- return _secondStep(inputData, outputData);
+ return _secondStep(opCtx, inputData);
}
- *outputData = "";
-
- return true;
+ return std::make_tuple(true, std::string{});
}
/*
@@ -86,8 +92,9 @@ static void decodeSCRAMUsername(std::string& user) {
*
* NOTE: we are ignoring the authorization ID part of the message
*/
-StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
- std::string* outputData) {
+template <typename Policy>
+StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_firstStep(
+ OperationContext* opCtx, StringData inputData) {
const auto badCount = [](int got) {
return Status(ErrorCodes::BadValue,
str::stream()
@@ -104,7 +111,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
* client-first-message := gs2-header client-first-message-bare
*/
const auto gs2_cbind_comma = inputData.find(',');
- if (gs2_cbind_comma == string::npos) {
+ if (gs2_cbind_comma == std::string::npos) {
return badCount(1);
}
const auto gs2_cbind_flag = inputData.substr(0, gs2_cbind_comma);
@@ -118,7 +125,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
}
const auto gs2_header_comma = inputData.find(',', gs2_cbind_comma + 1);
- if (gs2_header_comma == string::npos) {
+ if (gs2_header_comma == std::string::npos) {
return badCount(2);
}
auto authzId = inputData.substr(gs2_cbind_comma + 1, gs2_header_comma - (gs2_cbind_comma + 1));
@@ -155,18 +162,19 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
return Status(ErrorCodes::BadValue,
str::stream() << "Invalid SCRAM user name: " << input[0]);
}
- _user = input[0].substr(2);
- decodeSCRAMUsername(_user);
+ ServerMechanismBase::_principalName = input[0].substr(2);
+ decodeSCRAMUsername(ServerMechanismBase::_principalName);
- auto swUser = saslPrep(_user);
+ auto swUser = saslPrep(ServerMechanismBase::ServerMechanismBase::_principalName);
if (!swUser.isOK()) {
return swUser.getStatus();
}
- _user = std::move(swUser.getValue());
+ ServerMechanismBase::ServerMechanismBase::_principalName = std::move(swUser.getValue());
- if (!authzId.empty() && _user != authzId) {
+ if (!authzId.empty() && ServerMechanismBase::_principalName != authzId) {
return Status(ErrorCodes::BadValue,
- str::stream() << "SCRAM user name " << _user << " does not match authzid "
+ str::stream() << "SCRAM user name " << ServerMechanismBase::_principalName
+ << " does not match authzid "
<< authzId);
}
@@ -180,7 +188,8 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
// SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that
// cluster members may communicate with each other. Hence ignore disabled auth mechanism
// for the internal user.
- UserName user(_user, _saslAuthSession->getAuthenticationDatabase());
+ UserName user(ServerMechanismBase::ServerMechanismBase::_principalName,
+ ServerMechanismBase::getAuthenticationDatabase());
if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") &&
user != internalSecurity.user->getName()) {
return Status(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled");
@@ -188,20 +197,21 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
// The authentication database is also the source database for the user.
User* userObj;
- Status status =
- _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser(
- _saslAuthSession->getOpCtxt(), user, &userObj);
+ auto authManager = AuthorizationManager::get(opCtx->getServiceContext());
+ Status status = authManager->acquireUser(opCtx, user, &userObj);
if (!status.isOK()) {
return status;
}
- _creds = userObj->getCredentials();
+ User::CredentialData credentials = userObj->getCredentials();
UserName userName = userObj->getName();
- _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj);
+ authManager->releaseUser(userObj);
- if (!initAndValidateCredentials()) {
+ _scramCredentials = credentials.scram<HashBlock>();
+
+ if (!_scramCredentials.isValid()) {
// Check for authentication attempts of the __system user on
// systems started without a keyfile.
if (userName == internalSecurity.user->getName()) {
@@ -215,12 +225,16 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
}
}
+ _secrets = scram::Secrets<HashBlock>("",
+ base64::decode(_scramCredentials.storedKey),
+ base64::decode(_scramCredentials.serverKey));
+
// Generate server-first-message
// Create text-based nonce as base64 encoding of a binary blob of length multiple of 3
const int nonceLenQWords = 3;
uint64_t binaryNonce[nonceLenQWords];
- unique_ptr<SecureRandom> sr(SecureRandom::create());
+ std::unique_ptr<SecureRandom> sr(SecureRandom::create());
binaryNonce[0] = sr->nextInt64();
binaryNonce[1] = sr->nextInt64();
@@ -229,13 +243,14 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
_nonce =
clientNonce + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce));
StringBuilder sb;
- sb << "r=" << _nonce << ",s=" << getSalt() << ",i=" << getIterationCount();
- *outputData = sb.str();
+ sb << "r=" << _nonce << ",s=" << _scramCredentials.salt
+ << ",i=" << _scramCredentials.iterationCount;
+ std::string outputData = sb.str();
// add client-first-message-bare and server-first-message to _authMessage
- _authMessage = client_first_message_bare.toString() + "," + *outputData;
+ _authMessage = client_first_message_bare.toString() + "," + outputData;
- return false;
+ return std::make_tuple(false, std::move(outputData));
}
/**
@@ -250,8 +265,9 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData,
*
* NOTE: we are ignoring the channel binding part of the message
**/
-StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData,
- std::string* outputData) {
+template <typename Policy>
+StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_secondStep(
+ OperationContext* opCtx, StringData inputData) {
const auto badCount = [](int got) {
return Status(ErrorCodes::BadValue,
str::stream()
@@ -265,7 +281,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData,
* client-final-message := client-final-message-without-proof ',' proof
*/
const auto last_comma = inputData.rfind(',');
- if (last_comma == string::npos) {
+ if (last_comma == std::string::npos) {
return badCount(1);
}
@@ -315,18 +331,26 @@ StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData,
// ClientSignature := HMAC(StoredKey, AuthMessage)
// ClientKey := ClientSignature XOR ClientProof
// ServerSignature := HMAC(ServerKey, AuthMessage)
- invariant(initAndValidateCredentials());
- if (!verifyClientProof(base64::decode(proof.toString()))) {
+ if (!_secrets.verifyClientProof(_authMessage, base64::decode(proof.toString()))) {
return Status(ErrorCodes::AuthenticationFailed,
"SCRAM authentication failed, storedKey mismatch");
}
StringBuilder sb;
// ServerSignature := HMAC(ServerKey, AuthMessage)
- sb << "v=" << generateServerSignature();
- *outputData = sb.str();
+ sb << "v=" << _secrets.generateServerSignature(_authMessage);
- return false;
+ return std::make_tuple(false, sb.str());
}
+
+MONGO_INITIALIZER_WITH_PREREQUISITES(SASLSCRAMServerMechanism,
+ ("CreateSASLServerMechanismRegistry"))
+(::mongo::InitializerContext* context) {
+ auto& registry = SASLServerMechanismRegistry::get(getGlobalServiceContext());
+ registry.registerFactory<SCRAMSHA1ServerFactory>();
+ registry.registerFactory<SCRAMSHA256ServerFactory>();
+ return Status::OK();
+}
+
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.h b/src/mongo/db/auth/sasl_scram_server_conversation.h
index 718e168feea..33713258028 100644
--- a/src/mongo/db/auth/sasl_scram_server_conversation.h
+++ b/src/mongo/db/auth/sasl_scram_server_conversation.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 MongoDB Inc.
+ * 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,
@@ -28,28 +28,26 @@
#pragma once
-#include <string>
-#include <vector>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
-#include "mongo/base/status_with.h"
-#include "mongo/base/string_data.h"
#include "mongo/crypto/mechanism_scram.h"
-#include "mongo/db/auth/sasl_server_conversation.h"
+#include "mongo/db/auth/sasl_mechanism_policies.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/util/icu.h"
namespace mongo {
+
/**
* Server side authentication session for SASL SCRAM-SHA-1.
*/
-class SaslSCRAMServerConversation : public SaslServerConversation {
- MONGO_DISALLOW_COPYING(SaslSCRAMServerConversation);
-
+template <typename Policy>
+class SaslSCRAMServerMechanism : public MakeServerMechanism<Policy> {
public:
- explicit SaslSCRAMServerConversation(SaslAuthenticationSession* session)
- : SaslServerConversation(session) {}
- ~SaslSCRAMServerConversation() override = default;
+ using HashBlock = typename Policy::HashBlock;
+ static const bool isInternal = true;
+
+ explicit SaslSCRAMServerMechanism(std::string authenticationDatabase)
+ : MakeServerMechanism<Policy>(std::move(authenticationDatabase)) {}
+
+ ~SaslSCRAMServerMechanism() final = default;
/**
* Take one step in a SCRAM-SHA-1 conversation.
@@ -58,105 +56,51 @@ public:
* authentication conversation is finished or not.
*
**/
- StatusWith<bool> step(StringData inputData, std::string* outputData) override;
-
- /**
- * Initialize details, called after _creds has been loaded.
- */
- virtual bool initAndValidateCredentials() = 0;
-
- /**
- * Provide the predetermined salt to the client.
- */
- virtual std::string getSalt() const = 0;
-
- /**
- * Provide the predetermined iteration count to the client.
- */
- virtual size_t getIterationCount() const = 0;
+ StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
+ StringData inputData);
- /**
- * Verify proof submitted by authenticating client.
- */
- virtual bool verifyClientProof(StringData) const = 0;
-
- /**
- * Generate a signature to prove ourselves.
- */
- virtual std::string generateServerSignature() const = 0;
-
- /**
- * Runs saslPrep except on SHA-1.
- */
- virtual StatusWith<std::string> saslPrep(StringData str) const = 0;
+ StatusWith<std::string> saslPrep(StringData str) const {
+ if (std::is_same<SHA1Block, HashBlock>::value) {
+ return str.toString();
+ } else {
+ return mongo::saslPrep(str);
+ }
+ }
private:
/**
* Parse client-first-message and generate server-first-message
**/
- StatusWith<bool> _firstStep(StringData input, std::string* outputData);
+ StatusWith<std::tuple<bool, std::string>> _firstStep(OperationContext* opCtx, StringData input);
/**
* Parse client-final-message and generate server-final-message
**/
- StatusWith<bool> _secondStep(StringData input, std::string* outputData);
+ StatusWith<std::tuple<bool, std::string>> _secondStep(OperationContext* opCtx,
+ StringData input);
-protected:
int _step{0};
std::string _authMessage;
- User::CredentialData _creds;
+ User::SCRAMCredentials<HashBlock> _scramCredentials;
+ scram::Secrets<HashBlock> _secrets;
// client and server nonce concatenated
std::string _nonce;
};
-template <typename HashBlock>
-class SaslSCRAMServerConversationImpl : public SaslSCRAMServerConversation {
+template <typename ScramMechanism>
+class SCRAMServerFactory : public MakeServerFactory<ScramMechanism> {
public:
- explicit SaslSCRAMServerConversationImpl(SaslAuthenticationSession* session)
- : SaslSCRAMServerConversation(session) {}
- ~SaslSCRAMServerConversationImpl() override = default;
-
- bool initAndValidateCredentials() final {
- const auto& scram = _creds.scram<HashBlock>();
- if (!scram.isValid()) {
- return false;
- }
- if (!_credentials) {
- _credentials = scram::Secrets<HashBlock>(
- "", base64::decode(scram.storedKey), base64::decode(scram.serverKey));
- }
- return true;
- }
-
- std::string getSalt() const final {
- return _creds.scram<HashBlock>().salt;
- }
-
- size_t getIterationCount() const final {
- return _creds.scram<HashBlock>().iterationCount;
- }
-
- bool verifyClientProof(StringData clientProof) const final {
- return _credentials.verifyClientProof(_authMessage, clientProof);
- }
-
- std::string generateServerSignature() const final {
- return _credentials.generateServerSignature(_authMessage);
+ bool canMakeMechanismForUser(const User* user) const final {
+ auto credentials = user->getCredentials();
+ return credentials.scram<typename ScramMechanism::HashBlock>().isValid();
}
-
- StatusWith<std::string> saslPrep(StringData str) const final {
- if (std::is_same<SHA1Block, HashBlock>::value) {
- return str.toString();
- } else {
- return mongo::saslPrep(str);
- }
- }
-
-private:
- scram::Secrets<HashBlock> _credentials;
};
-using SaslSCRAMSHA1ServerConversation = SaslSCRAMServerConversationImpl<SHA1Block>;
+using SaslSCRAMSHA1ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA1Policy>;
+using SCRAMSHA1ServerFactory = SCRAMServerFactory<SaslSCRAMSHA1ServerMechanism>;
+
+using SaslSCRAMSHA256ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA256Policy>;
+using SCRAMSHA256ServerFactory = SCRAMServerFactory<SaslSCRAMSHA256ServerMechanism>;
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_scram_test.cpp b/src/mongo/db/auth/sasl_scram_test.cpp
index c75cae5f260..3ec3cb5c5d9 100644
--- a/src/mongo/db/auth/sasl_scram_test.cpp
+++ b/src/mongo/db/auth/sasl_scram_test.cpp
@@ -36,9 +36,10 @@
#include "mongo/crypto/sha1_block.h"
#include "mongo/crypto/sha256_block.h"
#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/authz_manager_external_state_mock.h"
#include "mongo/db/auth/authz_session_external_state_mock.h"
-#include "mongo/db/auth/native_sasl_authentication_session.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/auth/sasl_scram_server_conversation.h"
#include "mongo/db/service_context_noop.h"
#include "mongo/stdx/memory.h"
@@ -166,75 +167,39 @@ struct SCRAMStepsResult {
}
};
-SCRAMStepsResult runSteps(NativeSaslAuthenticationSession* saslServerSession,
- NativeSaslClientSession* saslClientSession,
- SCRAMMutators interposers = SCRAMMutators{}) {
- SCRAMStepsResult result{};
- std::string clientOutput = "";
- std::string serverOutput = "";
-
- for (size_t step = 1; step <= 3; step++) {
- ASSERT_FALSE(saslClientSession->isDone());
- ASSERT_FALSE(saslServerSession->isDone());
-
- // Client step
- result.status = saslClientSession->step(serverOutput, &clientOutput);
- if (result.status != Status::OK()) {
- return result;
- }
- interposers.execute(result.outcome, clientOutput);
- std::cout << result.outcome.toString() << ": " << clientOutput << std::endl;
- result.outcome.next();
-
- // Server step
- result.status = saslServerSession->step(clientOutput, &serverOutput);
- if (result.status != Status::OK()) {
- return result;
- }
- interposers.execute(result.outcome, serverOutput);
- std::cout << result.outcome.toString() << ": " << serverOutput << std::endl;
- result.outcome.next();
- }
- ASSERT_TRUE(saslClientSession->isDone());
- ASSERT_TRUE(saslServerSession->isDone());
-
- return result;
-}
-
class SCRAMFixture : public mongo::unittest::Test {
protected:
const SCRAMStepsResult goalState =
SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 4), Status::OK());
- ServiceContextNoop serviceContext;
+ std::unique_ptr<ServiceContextNoop> serviceContext;
ServiceContextNoop::UniqueClient client;
ServiceContextNoop::UniqueOperationContext opCtx;
AuthzManagerExternalStateMock* authzManagerExternalState;
- std::unique_ptr<AuthorizationManager> authzManager;
+ AuthorizationManager* authzManager;
std::unique_ptr<AuthorizationSession> authzSession;
- std::unique_ptr<NativeSaslAuthenticationSession> saslServerSession;
+ std::unique_ptr<ServerMechanismBase> saslServerSession;
std::unique_ptr<NativeSaslClientSession> saslClientSession;
void setUp() final {
- client = serviceContext.makeClient("test");
- opCtx = serviceContext.makeOperationContext(client.get());
+ serviceContext = stdx::make_unique<ServiceContextNoop>();
+ client = serviceContext->makeClient("test");
+ opCtx = serviceContext->makeOperationContext(client.get());
auto uniqueAuthzManagerExternalStateMock =
stdx::make_unique<AuthzManagerExternalStateMock>();
authzManagerExternalState = uniqueAuthzManagerExternalStateMock.get();
- authzManager =
- stdx::make_unique<AuthorizationManager>(std::move(uniqueAuthzManagerExternalStateMock));
+ authzManager = new AuthorizationManager(std::move(uniqueAuthzManagerExternalStateMock));
authzSession = stdx::make_unique<AuthorizationSession>(
- stdx::make_unique<AuthzSessionExternalStateMock>(authzManager.get()));
+ stdx::make_unique<AuthzSessionExternalStateMock>(authzManager));
+ AuthorizationManager::set(serviceContext.get(),
+ std::unique_ptr<AuthorizationManager>(authzManager));
- saslServerSession = stdx::make_unique<NativeSaslAuthenticationSession>(authzSession.get());
- saslServerSession->setOpCtxt(opCtx.get());
- ASSERT_OK(
- saslServerSession->start("test", _mechanism, "mongodb", "MockServer.test", 1, false));
saslClientSession = stdx::make_unique<NativeSaslClientSession>();
- saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism, _mechanism);
+ saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism,
+ saslServerSession->mechanismName());
saslClientSession->setParameter(NativeSaslClientSession::parameterServiceName, "mongodb");
saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostname,
"MockServer.test");
@@ -243,13 +208,15 @@ protected:
}
void tearDown() final {
+ opCtx.reset();
+ client.reset();
+ serviceContext.reset();
+
saslClientSession.reset();
saslServerSession.reset();
+
authzSession.reset();
- authzManager.reset();
authzManagerExternalState = nullptr;
- opCtx.reset();
- client.reset();
}
std::string createPasswordDigest(StringData username, StringData password) {
@@ -260,18 +227,55 @@ protected:
}
}
- std::string _mechanism;
+ SCRAMStepsResult runSteps(SCRAMMutators interposers = SCRAMMutators{}) {
+ SCRAMStepsResult result{};
+ std::string clientOutput = "";
+ std::string serverOutput = "";
+
+ for (size_t step = 1; step <= 3; step++) {
+ ASSERT_FALSE(saslClientSession->isDone());
+ ASSERT_FALSE(saslServerSession->isDone());
+
+ // Client step
+ result.status = saslClientSession->step(serverOutput, &clientOutput);
+ if (result.status != Status::OK()) {
+ return result;
+ }
+ interposers.execute(result.outcome, clientOutput);
+ std::cout << result.outcome.toString() << ": " << clientOutput << std::endl;
+ result.outcome.next();
+
+ // Server step
+ StatusWith<std::string> swServerResult =
+ saslServerSession->step(opCtx.get(), clientOutput);
+ result.status = swServerResult.getStatus();
+ if (result.status != Status::OK()) {
+ return result;
+ }
+ serverOutput = std::move(swServerResult.getValue());
+
+ interposers.execute(result.outcome, serverOutput);
+ std::cout << result.outcome.toString() << ": " << serverOutput << std::endl;
+ result.outcome.next();
+ }
+ ASSERT_TRUE(saslClientSession->isDone());
+ ASSERT_TRUE(saslServerSession->isDone());
+
+ return result;
+ }
+
+
bool _digestPassword;
public:
void run() {
log() << "SCRAM-SHA-1 variant";
- _mechanism = "SCRAM-SHA-1";
+ saslServerSession = std::make_unique<SaslSCRAMSHA1ServerMechanism>("test");
_digestPassword = true;
Test::run();
log() << "SCRAM-SHA-256 variant";
- _mechanism = "SCRAM-SHA-256";
+ saslServerSession = std::make_unique<SaslSCRAMSHA256ServerMechanism>("test");
_digestPassword = false;
Test::run();
}
@@ -297,7 +301,7 @@ TEST_F(SCRAMFixture, testServerStep1DoesNotIncludeNonceFromClientStep1) {
ASSERT_EQ(
SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 2),
Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testClientStep2DoesNotIncludeNonceFromServerStep1) {
@@ -319,7 +323,7 @@ TEST_F(SCRAMFixture, testClientStep2DoesNotIncludeNonceFromServerStep1) {
ASSERT_EQ(
SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2),
Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testClientStep2GivesBadProof) {
@@ -345,7 +349,7 @@ TEST_F(SCRAMFixture, testClientStep2GivesBadProof) {
Status(ErrorCodes::AuthenticationFailed,
"SCRAM authentication failed, storedKey mismatch")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testServerStep2GivesBadVerifier) {
@@ -371,7 +375,7 @@ TEST_F(SCRAMFixture, testServerStep2GivesBadVerifier) {
});
- auto result = runSteps(saslServerSession.get(), saslClientSession.get(), mutator);
+ auto result = runSteps(mutator);
ASSERT_EQ(SCRAMStepsResult(
SaslTestState(SaslTestState::kClient, 3),
@@ -392,7 +396,7 @@ TEST_F(SCRAMFixture, testSCRAM) {
ASSERT_OK(saslClientSession->initialize());
- ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get()));
+ ASSERT_EQ(goalState, runSteps());
}
TEST_F(SCRAMFixture, testSCRAMWithChannelBindingSupportedByClient) {
@@ -410,7 +414,7 @@ TEST_F(SCRAMFixture, testSCRAMWithChannelBindingSupportedByClient) {
clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "y");
});
- ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ ASSERT_EQ(goalState, runSteps(mutator));
}
TEST_F(SCRAMFixture, testSCRAMWithChannelBindingRequiredByClient) {
@@ -431,7 +435,7 @@ TEST_F(SCRAMFixture, testSCRAMWithChannelBindingRequiredByClient) {
ASSERT_EQ(
SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1),
Status(ErrorCodes::BadValue, "Server does not support channel binding")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testSCRAMWithInvalidChannelBinding) {
@@ -452,7 +456,7 @@ TEST_F(SCRAMFixture, testSCRAMWithInvalidChannelBinding) {
ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1),
Status(ErrorCodes::BadValue,
"Incorrect SCRAM client message prefix: v=illegalGarbage")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testNULLInPassword) {
@@ -465,7 +469,7 @@ TEST_F(SCRAMFixture, testNULLInPassword) {
ASSERT_OK(saslClientSession->initialize());
- ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get()));
+ ASSERT_EQ(goalState, runSteps());
}
@@ -479,7 +483,7 @@ TEST_F(SCRAMFixture, testCommasInUsernameAndPassword) {
ASSERT_OK(saslClientSession->initialize());
- ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get()));
+ ASSERT_EQ(goalState, runSteps());
}
TEST_F(SCRAMFixture, testIncorrectUser) {
@@ -491,7 +495,7 @@ TEST_F(SCRAMFixture, testIncorrectUser) {
ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1),
Status(ErrorCodes::UserNotFound, "Could not find user sajack@test")),
- runSteps(saslServerSession.get(), saslClientSession.get()));
+ runSteps());
}
TEST_F(SCRAMFixture, testIncorrectPassword) {
@@ -507,7 +511,7 @@ TEST_F(SCRAMFixture, testIncorrectPassword) {
ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2),
Status(ErrorCodes::AuthenticationFailed,
"SCRAM authentication failed, storedKey mismatch")),
- runSteps(saslServerSession.get(), saslClientSession.get()));
+ runSteps());
}
TEST_F(SCRAMFixture, testOptionalClientExtensions) {
@@ -531,7 +535,7 @@ TEST_F(SCRAMFixture, testOptionalClientExtensions) {
ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2),
Status(ErrorCodes::AuthenticationFailed,
"SCRAM authentication failed, storedKey mismatch")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
TEST_F(SCRAMFixture, testOptionalServerExtensions) {
@@ -556,7 +560,7 @@ TEST_F(SCRAMFixture, testOptionalServerExtensions) {
ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2),
Status(ErrorCodes::AuthenticationFailed,
"SCRAM authentication failed, storedKey mismatch")),
- runSteps(saslServerSession.get(), saslClientSession.get(), mutator));
+ runSteps(mutator));
}
template <typename HashBlock>
diff --git a/src/mongo/db/auth/sasl_server_conversation.cpp b/src/mongo/db/auth/sasl_server_conversation.cpp
deleted file mode 100644
index 75f680e9a0d..00000000000
--- a/src/mongo/db/auth/sasl_server_conversation.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
- *
- * 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/db/auth/sasl_server_conversation.h"
-
-#include <string>
-
-namespace mongo {
-
-SaslServerConversation::~SaslServerConversation(){};
-
-std::string SaslServerConversation::getPrincipalId() {
- return _user;
-}
-
-} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_server_conversation.h b/src/mongo/db/auth/sasl_server_conversation.h
deleted file mode 100644
index 5d0ce497523..00000000000
--- a/src/mongo/db/auth/sasl_server_conversation.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
- *
- * 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 <string>
-
-#include "mongo/base/disallow_copying.h"
-#include "mongo/base/status.h"
-#include "mongo/base/string_data.h"
-#include "mongo/db/auth/sasl_authentication_session.h"
-#include "mongo/db/auth/user.h"
-
-namespace mongo {
-
-class SaslAuthenticationSession;
-template <typename T>
-class StatusWith;
-
-/**
- * Abstract class for implementing the server-side
- * of a SASL mechanism conversation.
- */
-class SaslServerConversation {
- MONGO_DISALLOW_COPYING(SaslServerConversation);
-
-public:
- /**
- * Implements the server side of a SASL authentication mechanism.
- *
- * "saslAuthSession" is the corresponding SASLAuthenticationSession.
- * "saslAuthSession" must stay in scope until the SaslServerConversation's
- * destructor completes.
- *
- **/
- explicit SaslServerConversation(SaslAuthenticationSession* saslAuthSession)
- : _saslAuthSession(saslAuthSession), _user("") {}
-
- virtual ~SaslServerConversation();
-
- /**
- * Performs one step of the server side of the authentication session,
- * consuming "inputData" and producing "*outputData".
- *
- * A return of Status::OK() indicates successful progress towards authentication.
- * A return of !Status::OK() indicates failed authentication
- *
- * A return of true means that the authentication process has finished.
- * A return of false means that the authentication process has more steps.
- *
- */
- virtual StatusWith<bool> step(StringData inputData, std::string* outputData) = 0;
-
- /**
- * Gets the SASL principal id (user name) for the conversation
- **/
- std::string getPrincipalId();
-
-protected:
- SaslAuthenticationSession* _saslAuthSession;
- std::string _user;
-};
-
-} // namespace mongo
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index ab9c0dc2227..5cebfed131d 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -1522,6 +1522,7 @@ env.Library(
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/client/clientdriver',
'$BUILD_DIR/mongo/db/auth/authcore',
+ '$BUILD_DIR/mongo/db/auth/saslauth',
'$BUILD_DIR/mongo/db/dbhelpers',
'$BUILD_DIR/mongo/db/query_exec',
'oplog',
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index 396c41ff260..fc33d735f13 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -33,7 +33,7 @@
#include <vector>
#include "mongo/client/connpool.h"
-#include "mongo/db/auth/sasl_mechanism_advertiser.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/client.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/db_raii.h"
@@ -400,7 +400,8 @@ public:
.serverNegotiate(cmdObj, &result);
}
- SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result);
+ auto& saslMechanismRegistry = SASLServerMechanismRegistry::get(opCtx->getServiceContext());
+ saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, cmdObj, &result);
return true;
}
diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp
index eaee3701cec..2b45078ab2d 100644
--- a/src/mongo/s/commands/cluster_is_master_cmd.cpp
+++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp
@@ -28,7 +28,7 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/auth/sasl_mechanism_advertiser.h"
+#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
#include "mongo/db/logical_session_id.h"
@@ -133,7 +133,8 @@ public:
MessageCompressorManager::forSession(opCtx->getClient()->session())
.serverNegotiate(cmdObj, &result);
- SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result);
+ auto& saslMechanismRegistry = SASLServerMechanismRegistry::get(opCtx->getServiceContext());
+ saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, cmdObj, &result);
return true;
}