Browse files

SERVER-12369 SERVER-11461 Update mongodump and mongorestore to proper…

…ly handle users and roles in 2.6

There are some known caveats:
(1) It's not safe to run mongorestore while user-management commands are
executing on a live system (SERVER-12539).

(2) To restore 2.4 users to a 2.6 system that has never had any users added, you
must manually insert the version document indicating v1 users into
admin.system.version, which must be done by a highly privileged user, before
beginning the restore (SERVER-12541).
  • Loading branch information...
1 parent fc2cbaf commit dac6264fb0f0040f7bd8784ed44c33e4a1318d5b @stbrody stbrody committed Jan 21, 2014
View
2 jstests/tool/dumprestore_auth2.js
@@ -45,7 +45,7 @@ t.runTool("restore", "--dir", t.ext, "--drop")
assert.soon("1 == db.system.users.find({user:'user'}).count()", "didn't restore users 2")
assert.eq(0, db.system.users.find({user:'user2'}).count(), "didn't drop users")
-// assert.eq(0, db.system.roles.find({role:'role2'}).count(), "didn't drop roles") // SERVER-11461
+assert.eq(0, db.system.roles.find({role:'role2'}).count(), "didn't drop roles")
assert.eq(1, db.system.roles.find({role:'role'}).count(), "didn't restore roles")
assert.eq(2, db.system.indexes.count({ns: "admin.system.users"}), "didn't maintain user indexes")
assert.eq(2, db.system.indexes.count({ns: "admin.system.roles"}), "didn't maintain role indexes")
View
189 jstests/tool/dumprestore_auth3.js
@@ -0,0 +1,189 @@
+// dumprestore_auth3.js
+// Tests that mongodump and mongorestore properly handle access control information when doing
+// single-db dumps and restores
+
+// Runs the tool with the given name against the given mongod. If shutdownServer is true,
+// first shuts down the mongod and uses the --dbpath option to the tool to operate on the data
+// files directly
+function runTool(toolName, mongod, shutdownServer, options) {
+ if (shutdownServer) {
+ MongoRunner.stopMongod(mongod);
+ var opts = {dbpath: mongod.fullOptions.pathOpts.dbpath};
+ Object.extend(opts, options);
+ assert(!MongoRunner.runMongoTool(toolName, opts));
+ mongod.fullOptions.restart = true;
+ return MongoRunner.runMongod(mongod.fullOptions);
+ } else {
+ var opts = {host: mongod.host};
+ Object.extend(opts, options);
+ assert(!MongoRunner.runMongoTool(toolName, opts));
+ return mongod;
+ }
+}
+
+// If shutdownServer is true, will run tools against shut down mongod, operating on the data
+// files directly
+function runTest(shutdownServer) {
+ var mongod = MongoRunner.runMongod();
+ var db = mongod.getDB("foo");
+
+ jsTestLog("Creating initial data");
+ db.createUser({user: 'user', pwd: 'password', roles: jsTest.basicUserRoles});
+ db.createRole({role: 'role', roles: [], privileges:[]});
+ // Legacy system.users collections should still be handled properly
+ db.system.users.insert({user:'dbuser', pwd: 'pwd', roles: ['readWrite']});
+ db.bar.insert({a:1});
+
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(1, db.getUsers().length, "setup");
+ assert.eq(1, db.getRoles().length, "setup2");
+ assert.eq(1, db.system.users.count(), "setup3");
+ assert.eq(1, db.getSiblingDB('admin').system.version.count());
+ var versionDoc = db.getSiblingDB('admin').system.version.findOne();
+
+ jsTestLog("Dump foo database without dumping user data");
+ var dumpDir = MongoRunner.getAndPrepareDumpDirectory("dumprestore_auth3");
+ mongod = runTool("mongodump", mongod, shutdownServer, {out: dumpDir, db: "foo"});
+ db = mongod.getDB('foo');
+
+ db.dropDatabase();
+ db.dropAllUsers();
+ db.dropAllRoles();
+
+ assert.eq(0, db.getUsers().length, "didn't drop users");
+ assert.eq(0, db.getRoles().length, "didn't drop roles");
+ assert.eq(0, db.system.users.count(), "didn't drop legacy system.users collection");
+ assert.eq(0, db.bar.count(), "didn't drop 'bar' collection");
+
+
+ jsTestLog("Restore foo database from dump that doesn't contain user data");
+ mongod = runTool("mongorestore", mongod, shutdownServer, {dir: dumpDir + "foo/",
+ db: 'foo',
+ restoreDbUsersAndRoles: ""});
+ db = mongod.getDB('foo');
+
+ assert.soon(function() { return db.bar.findOne(); }, "no data after restore");
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(0, db.getUsers().length, "Restore created users somehow");
+ assert.eq(0, db.getRoles().length, "Restore created roles somehow");
+ assert.eq(0, db.system.users.count(), "Restore created legacy system.users collection somehow");
+
+ // Re-create user data
+ db.createUser({user: 'user', pwd: 'password', roles: jsTest.basicUserRoles});
+ db.createRole({role: 'role', roles: [], privileges:[]});
+ db.system.users.insert({user:'dbuser', pwd: 'pwd', roles: ['readWrite']});
+
+
+ jsTestLog("Dump foo database *with* user data");
+ mongod = runTool("mongodump", mongod, shutdownServer, {out: dumpDir,
+ db: "foo",
+ dumpDbUsersAndRoles: ""});
+ db = mongod.getDB('foo');
+
+ db.dropDatabase();
+ db.dropAllUsers();
+ db.dropAllRoles();
+
+ assert.eq(0, db.getUsers().length, "didn't drop users");
+ assert.eq(0, db.getRoles().length, "didn't drop roles");
+ assert.eq(0, db.system.users.count(), "didn't drop legacy system.users collection");
+ assert.eq(0, db.bar.count(), "didn't drop 'bar' collection");
+
+ jsTestLog("Restore foo database without restoring user data, even though it's in the dump");
+ mongod = runTool("mongorestore", mongod, shutdownServer, {dir: dumpDir + "foo/", db: 'foo'});
+ db = mongod.getDB('foo');
+
+ assert.soon(function() { return db.bar.findOne(); }, "no data after restore");
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(0, db.getUsers().length, "Restored users even though it shouldn't have");
+ assert.eq(0, db.getRoles().length, "Restored users even though it shouldn't have");
+
+ jsTestLog("Restore foo database *with* user data");
+ mongod = runTool("mongorestore", mongod, shutdownServer, {dir: dumpDir + "foo/",
+ db: 'foo',
+ restoreDbUsersAndRoles: ""});
+ db = mongod.getDB('foo');
+
+ assert.soon(function() { return db.bar.findOne(); }, "no data after restore");
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(1, db.getUsers().length, "didn't restore users");
+ assert.eq(1, db.getRoles().length, "didn't restore roles");
+ assert.eq(1, db.system.users.count(), "didn't restore legacy system.users collection");
+ assert.docEq(versionDoc,
+ db.getSiblingDB('admin').system.version.findOne(),
+ "version doc was changed by restore");
+
+
+ jsTestLog("Make modifications to user data that should be overridden by the restore");
+ db.dropUser('user')
+ db.createUser({user: 'user2', pwd: 'password2', roles: jsTest.basicUserRoles});
+ db.dropRole('role')
+ db.createRole({role: 'role2', roles: [], privileges:[]});
+ db.system.users.remove();
+ db.system.users.insert({user:'dbuser2', pwd: 'pwd', roles: ['readWrite']});
+
+ jsTestLog("Restore foo database (and user data) with --drop so it overrides the changes made");
+ // Restore with --drop to override the changes to user data
+ mongod = runTool("mongorestore", mongod, shutdownServer, {dir: dumpDir + "foo/",
+ db: 'foo',
+ drop: "",
+ restoreDbUsersAndRoles: ""});
+ db = mongod.getDB('foo');
+
+ assert.soon(function() { return db.bar.findOne(); }, "no data after restore");
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(1, db.getUsers().length, "didn't restore users");
+ assert.eq("user", db.getUsers()[0].user, "didn't update user");
+ assert.eq(1, db.getRoles().length, "didn't restore roles");
+ assert.eq("role", db.getRoles()[0].role, "didn't update role");
+ assert.eq(1, db.system.users.count(), "didn't restore legacy system.users collection");
+ assert.eq("dbuser", db.system.users.findOne().user, "didn't update legacy user");
+ assert.docEq(versionDoc,
+ db.getSiblingDB('admin').system.version.findOne(),
+ "version doc was changed by restore");
+
+
+ jsTestLog("Dump just the admin database. User data should be dumped by default");
+ // Make a user in another database to make sure it is properly captured
+ db.getSiblingDB('bar').createUser({user: "user", pwd: 'pwd', roles: []});
+ db.getSiblingDB('admin').createUser({user: "user", pwd: 'pwd', roles: []});
+ mongod = runTool("mongodump", mongod, shutdownServer, {out: dumpDir, db: "admin"});
+ db = mongod.getDB('foo');
+
+ // Change user data a bit.
+ db.dropAllUsers();
+ db.getSiblingDB('bar').createUser({user: "user2", pwd: 'pwd', roles: []});
+ db.getSiblingDB('admin').dropAllUsers();
+
+ jsTestLog("Restore just the admin database. User data should be restored by default");
+ mongod = runTool("mongorestore", mongod, shutdownServer, {dir: dumpDir + "admin/",
+ db: 'admin',
+ drop: ""});
+ db = mongod.getDB('foo');
+ var otherdb = db.getSiblingDB('bar');
+ var admindb = db.getSiblingDB('admin');
+
+ assert.soon(function() { return db.bar.findOne(); }, "no data after restore");
+ assert.eq(1, db.bar.findOne().a);
+ assert.eq(1, db.getUsers().length, "didn't restore users");
+ assert.eq("user", db.getUsers()[0].user, "didn't restore user");
+ assert.eq(1, db.getRoles().length, "didn't restore roles");
+ assert.eq("role", db.getRoles()[0].role, "didn't restore role");
+ assert.eq(1, db.system.users.count(), "didn't restore legacy system.users collection");
+ assert.eq("dbuser", db.system.users.findOne().user, "didn't restore legacy user");
+ assert.eq(1, db.getUsers().length, "didn't restore users for bar database");
+ assert.eq("user", db.getUsers()[0].user, "didn't restore user for bar database");
+ assert.eq(1, admindb.getUsers().length, "didn't restore users for admin database");
+ assert.eq("user", admindb.getUsers()[0].user, "didn't restore user for admin database");
+ assert.eq(3, admindb.system.users.count(), "has the wrong # of users for the whole server");
+ assert.eq(1, admindb.system.roles.count(), "has the wrong # of roles for the whole server");
+ assert.docEq(versionDoc,
+ db.getSiblingDB('admin').system.version.findOne(),
+ "version doc was changed by restore");
+
+
+ MongoRunner.stopMongod(mongod);
+}
+
+runTest(false);
+runTest(true);
View
133 src/mongo/client/auth_helpers.cpp
@@ -16,11 +16,15 @@
#include "mongo/client/auth_helpers.h"
#include "mongo/base/string_data.h"
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/db/auth/authorization_manager.h"
#include "mongo/util/md5.hpp"
namespace mongo {
namespace auth {
+ const std::string schemaVersionServerParameter = "authSchemaVersion";
+
std::string createPasswordDigest(const StringData& username,
const StringData& clearTextPassword) {
md5digest d;
@@ -37,5 +41,134 @@ namespace auth {
return digestToString( d );
}
+ Status getRemoteStoredAuthorizationVersion(DBClientBase* conn, int* outVersion) {
+ try {
+ BSONObj cmdResult;
+ conn->runCommand(
+ "admin",
+ BSON("getParameter" << 1 << schemaVersionServerParameter << 1),
+ cmdResult);
+ if (!cmdResult["ok"].trueValue()) {
+ std::string errmsg = cmdResult["errmsg"].str();
+ if (errmsg == "no option found to get" ||
+ StringData(errmsg).startsWith("no such cmd")) {
+
+ *outVersion = 1;
+ return Status::OK();
+ }
+ int code = cmdResult["code"].numberInt();
+ if (code == 0) {
+ code = ErrorCodes::UnknownError;
+ }
+ return Status(ErrorCodes::Error(code), errmsg);
+ }
+ BSONElement versionElement = cmdResult[schemaVersionServerParameter];
+ if (versionElement.eoo())
+ return Status(ErrorCodes::UnknownError, "getParameter misbehaved.");
+ *outVersion = versionElement.numberInt();
+ return Status::OK();
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+ }
+
+ void getUpdateToUpgradeUser(const StringData& sourceDB,
+ const BSONObj& oldUserDoc,
+ BSONObj* query,
+ BSONObj* update) {
+ std::string oldUserSource;
+ uassertStatusOK(bsonExtractStringFieldWithDefault(
+ oldUserDoc,
+ "userSource",
+ sourceDB,
+ &oldUserSource));
+
+ const std::string oldUserName = oldUserDoc["user"].String();
+ *query = BSON("_id" << oldUserSource + "." + oldUserName);
+
+ BSONObjBuilder updateBuilder;
+
+ {
+ BSONObjBuilder toSetBuilder(updateBuilder.subobjStart("$set"));
+ toSetBuilder << "user" << oldUserName << "db" << oldUserSource;
+ BSONElement pwdElement = oldUserDoc["pwd"];
+ if (!pwdElement.eoo()) {
+ toSetBuilder << "credentials" << BSON("MONGODB-CR" << pwdElement.String());
+ }
+ else if (oldUserSource == "$external") {
+ toSetBuilder << "credentials" << BSON("external" << true);
+ }
+ }
+ {
+ BSONObjBuilder pushAllBuilder(updateBuilder.subobjStart("$pushAll"));
+ BSONArrayBuilder rolesBuilder(pushAllBuilder.subarrayStart("roles"));
+
+ const bool readOnly = oldUserDoc["readOnly"].trueValue();
+ const BSONElement rolesElement = oldUserDoc["roles"];
+ if (readOnly) {
+ // Handles the cases where there is a truthy readOnly field, which is a 2.2-style
+ // read-only user.
+ if (sourceDB == "admin") {
+ rolesBuilder << BSON("role" << "readAnyDatabase" << "db" << "admin");
+ }
+ else {
+ rolesBuilder << BSON("role" << "read" << "db" << sourceDB);
+ }
+ }
+ else if (rolesElement.eoo()) {
+ // Handles the cases where the readOnly field is absent or falsey, but the
+ // user is known to be 2.2-style because it lacks a roles array.
+ if (sourceDB == "admin") {
+ rolesBuilder << BSON("role" << "root" << "db" << "admin");
+ }
+ else {
+ rolesBuilder << BSON("role" << "dbOwner" << "db" << sourceDB);
+ }
+ }
+ else {
+ // Handles 2.4-style user documents, with roles arrays and (optionally, in admin db)
+ // otherDBRoles objects.
+ uassert(17252,
+ "roles field in v2.4 user documents must be an array",
+ rolesElement.type() == Array);
+ for (BSONObjIterator oldRoles(rolesElement.Obj());
+ oldRoles.more();
+ oldRoles.next()) {
+
+ BSONElement roleElement = *oldRoles;
+ rolesBuilder << BSON("role" << roleElement.String() << "db" << sourceDB);
+ }
+
+ BSONElement otherDBRolesElement = oldUserDoc["otherDBRoles"];
+ if (sourceDB == "admin" && !otherDBRolesElement.eoo()) {
+ uassert(17253,
+ "otherDBRoles field in v2.4 user documents must be an object.",
+ otherDBRolesElement.type() == Object);
+
+ for (BSONObjIterator otherDBs(otherDBRolesElement.Obj());
+ otherDBs.more();
+ otherDBs.next()) {
+
+ BSONElement otherDBRoles = *otherDBs;
+ if (otherDBRoles.fieldNameStringData() == "local")
+ continue;
+ uassert(17254,
+ "Member fields of otherDBRoles objects must be arrays.",
+ otherDBRoles.type() == Array);
+ for (BSONObjIterator oldRoles(otherDBRoles.Obj());
+ oldRoles.more();
+ oldRoles.next()) {
+
+ BSONElement roleElement = *oldRoles;
+ rolesBuilder << BSON("role" << roleElement.String() <<
+ "db" << otherDBRoles.fieldNameStringData());
+ }
+ }
+ }
+ }
+ }
+
+ *update = updateBuilder.obj();
+ }
} // namespace auth
} // namespace mongo
View
22 src/mongo/client/auth_helpers.h
@@ -15,7 +15,9 @@
#pragma once
+#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
+#include "mongo/client/dbclientinterface.h"
#include "mongo/client/export_macros.h"
namespace mongo {
@@ -28,5 +30,25 @@ namespace auth {
std::string MONGO_CLIENT_API createPasswordDigest(const StringData& username,
const StringData& clearTextPassword);
+ /**
+ * Retrieves the schema version of the persistent data describing users and roles from the
+ * remote server connected to with conn.
+ */
+ Status getRemoteStoredAuthorizationVersion(DBClientBase* conn, int* outVersion);
+
+ /**
+ * Given a schemaVersion24 user document and its source database, return the query and update
+ * specifier needed to upsert a schemaVersion26 version of the user.
+ */
+ void getUpdateToUpgradeUser(const StringData& sourceDB,
+ const BSONObj& oldUserDoc,
+ BSONObj* query,
+ BSONObj* update);
+
+ /**
+ * Name of the server parameter used to report the auth schema version (via getParameter).
+ */
+ extern const std::string schemaVersionServerParameter;
+
} // namespace auth
} // namespace mongo
View
90 src/mongo/db/auth/authorization_manager.cpp
@@ -40,6 +40,7 @@
#include "mongo/bson/mutable/document.h"
#include "mongo/bson/mutable/element.h"
#include "mongo/bson/util/bson_extract.h"
+#include "mongo/client/auth_helpers.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/authz_documents_update_guard.h"
#include "mongo/db/auth/authz_manager_external_state.h"
@@ -96,7 +97,6 @@ namespace mongo {
const BSONObj AuthorizationManager::versionDocumentQuery = BSON("_id" << "authSchema");
- const std::string AuthorizationManager::schemaVersionServerParameter = "authSchemaVersion";
const std::string AuthorizationManager::schemaVersionFieldName = "currentVersion";
#ifndef _MSC_EXTENSIONS
@@ -923,92 +923,10 @@ namespace {
if (oldUserSource == "local")
return; // Skips users from "local" database, which cannot be upgraded.
- const std::string oldUserName = oldUserDoc["user"].String();
- BSONObj query = BSON("_id" << oldUserSource + "." + oldUserName);
+ BSONObj query;
+ BSONObj update;
+ auth::getUpdateToUpgradeUser(sourceDB, oldUserDoc, &query, &update);
- BSONObjBuilder updateBuilder;
-
- {
- BSONObjBuilder toSetBuilder(updateBuilder.subobjStart("$set"));
- toSetBuilder << "user" << oldUserName << "db" << oldUserSource;
- BSONElement pwdElement = oldUserDoc["pwd"];
- if (!pwdElement.eoo()) {
- toSetBuilder << "credentials" << BSON("MONGODB-CR" << pwdElement.String());
- }
- else if (oldUserSource == "$external") {
- toSetBuilder << "credentials" << BSON("external" << true);
- }
- }
- {
- BSONObjBuilder pushAllBuilder(updateBuilder.subobjStart("$pushAll"));
- BSONArrayBuilder rolesBuilder(pushAllBuilder.subarrayStart("roles"));
-
- const bool readOnly = oldUserDoc["readOnly"].trueValue();
- const BSONElement rolesElement = oldUserDoc["roles"];
- if (readOnly) {
- // Handles the cases where there is a truthy readOnly field, which is a 2.2-style
- // read-only user.
- if (sourceDB == "admin") {
- rolesBuilder << BSON("role" << "readAnyDatabase" << "db" << "admin");
- }
- else {
- rolesBuilder << BSON("role" << "read" << "db" << sourceDB);
- }
- }
- else if (rolesElement.eoo()) {
- // Handles the cases where the readOnly field is absent or falsey, but the
- // user is known to be 2.2-style because it lacks a roles array.
- if (sourceDB == "admin") {
- rolesBuilder << BSON("role" << "root" << "db" << "admin");
- }
- else {
- rolesBuilder << BSON("role" << "dbOwner" << "db" << sourceDB);
- }
- }
- else {
- // Handles 2.4-style user documents, with roles arrays and (optionally, in admin db)
- // otherDBRoles objects.
- uassert(17252,
- "roles field in v2.4 user documents must be an array",
- rolesElement.type() == Array);
- for (BSONObjIterator oldRoles(rolesElement.Obj());
- oldRoles.more();
- oldRoles.next()) {
-
- BSONElement roleElement = *oldRoles;
- rolesBuilder << BSON("role" << roleElement.String() << "db" << sourceDB);
- }
-
- BSONElement otherDBRolesElement = oldUserDoc["otherDBRoles"];
- if (sourceDB == "admin" && !otherDBRolesElement.eoo()) {
- uassert(17253,
- "otherDBRoles field in v2.4 user documents must be an object.",
- otherDBRolesElement.type() == Object);
-
- for (BSONObjIterator otherDBs(otherDBRolesElement.Obj());
- otherDBs.more();
- otherDBs.next()) {
-
- BSONElement otherDBRoles = *otherDBs;
- if (otherDBRoles.fieldNameStringData() == "local")
- continue;
- uassert(17254,
- "Member fields of otherDBRoles objects must be arrays.",
- otherDBRoles.type() == Array);
- for (BSONObjIterator oldRoles(otherDBRoles.Obj());
- oldRoles.more();
- oldRoles.next()) {
-
- BSONElement roleElement = *oldRoles;
- rolesBuilder << BSON("role" << roleElement.String() <<
- "db" << otherDBRoles.fieldNameStringData());
- }
- }
- }
- }
- }
-
- BSONObj update = updateBuilder.done();
uassertStatusOK(externalState->updateOne(
AuthorizationManager::usersAltCollectionNamespace,
query,
View
5 src/mongo/db/auth/authorization_manager.h
@@ -95,11 +95,6 @@ namespace mongo {
static const BSONObj versionDocumentQuery;
/**
- * Name of the server parameter used to report the auth schema version (via getParameter).
- */
- static const std::string schemaVersionServerParameter;
-
- /**
* Name of the field in the auth schema version document containing the current schema
* version.
*/
View
3 src/mongo/db/auth/authorization_manager_global.cpp
@@ -30,6 +30,7 @@
#include "mongo/base/disallow_copying.h"
#include "mongo/base/init.h"
+#include "mongo/client/auth_helpers.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/server_parameters.h"
@@ -52,7 +53,7 @@ namespace {
MONGO_NO_PREREQUISITES,
("BeginStartupOptionParsing"))(InitializerContext*) {
new AuthzVersionParameter(ServerParameterSet::getGlobal(),
- AuthorizationManager::schemaVersionServerParameter);
+ auth::schemaVersionServerParameter);
return Status::OK();
}
View
40 src/mongo/db/auth/authz_manager_external_state_s.cpp
@@ -32,6 +32,7 @@
#include <boost/scoped_ptr.hpp>
#include <string>
+#include "mongo/client/auth_helpers.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/user_name.h"
@@ -70,40 +71,11 @@ namespace mongo {
}
Status AuthzManagerExternalStateMongos::getStoredAuthorizationVersion(int* outVersion) {
- try {
- scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection(
- AuthorizationManager::usersCollectionNamespace));
- BSONObj cmdResult;
- conn->get()->runCommand(
- "admin",
- BSON("getParameter" << 1 <<
- AuthorizationManager::schemaVersionServerParameter << 1),
- cmdResult);
- if (!cmdResult["ok"].trueValue()) {
- std::string errmsg = cmdResult["errmsg"].str();
- if (errmsg == "no option found to get" ||
- StringData(errmsg).startsWith("no such cmd")) {
-
- *outVersion = 1;
- conn->done();
- return Status::OK();
- }
- int code = cmdResult["code"].numberInt();
- if (code == 0) {
- code = ErrorCodes::UnknownError;
- }
- return Status(ErrorCodes::Error(code), errmsg);
- }
- BSONElement versionElement =
- cmdResult[AuthorizationManager::schemaVersionServerParameter];
- if (versionElement.eoo())
- return Status(ErrorCodes::UnknownError, "getParameter misbehaved.");
- *outVersion = versionElement.numberInt();
- conn->done();
- return Status::OK();
- } catch (const DBException& e) {
- return e.toStatus();
- }
+ scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection(
+ AuthorizationManager::usersCollectionNamespace));
+ Status status = auth::getRemoteStoredAuthorizationVersion(conn->get(), outVersion);
+ conn->done();
+ return status;
}
Status AuthzManagerExternalStateMongos::getUserDescription(const UserName& userName,
View
10 src/mongo/db/auth/role_graph_builtin_roles.cpp
@@ -471,8 +471,9 @@ namespace {
Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find));
ActionSet clusterActions;
- clusterActions << ActionType::listDatabases
- << ActionType::appendOplogNote;
+ clusterActions << ActionType::getParameter // To check authSchemaVersion
+ << ActionType::listDatabases
+ << ActionType::appendOplogNote; // For BRS
Privilege::addPrivilegeToPrivilegeVector(
privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions));
@@ -569,6 +570,11 @@ namespace {
privileges,
Privilege(ResourcePattern::forCollectionName("system.namespaces"),
ActionType::find));
+
+ // Need to be able to run getParameter to check authSchemaVersion
+ Privilege::addPrivilegeToPrivilegeVector(
+ privileges, Privilege(ResourcePattern::forClusterResource(),
+ ActionType::getParameter));
}
void addRootRolePrivileges(PrivilegeVector* privileges) {
View
11 src/mongo/shell/servers.js
@@ -415,7 +415,8 @@ MongoRunner.mongoOptions = function( opts ){
}
// Default for waitForConnect is true
- if (waitForConnect == undefined || waitForConnect == null) opts.waitForConnect = true;
+ opts.waitForConnect = (waitForConnect == undefined || waitForConnect == null) ?
+ true : waitForConnect;
if( jsTestOptions().useSSL ) {
if (!opts.sslMode) opts.sslMode = "requireSSL";
@@ -708,6 +709,14 @@ MongoRunner.runMongoTool = function( binaryName, opts ){
}
+// Given a test name figures out a directory for that test to use for dump files and makes sure
+// that directory exists and is empty.
+MongoRunner.getAndPrepareDumpDirectory = function(testName) {
+ var dir = MongoRunner.dataPath + testName + "_external/";
+ resetDbpath(dir);
+ return dir;
+}
+
startMongodTest = function (port, dirname, restart, extraOptions ) {
if (!port)
port = MongoRunner.nextOpenPort();
View
76 src/mongo/tools/dump.cpp
@@ -34,13 +34,17 @@
#include <fstream>
#include <map>
+#include "mongo/base/status.h"
+#include "mongo/client/auth_helpers.h"
#include "mongo/client/dbclientcursor.h"
+#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/db.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/tools/mongodump_options.h"
#include "mongo/tools/tool.h"
#include "mongo/util/options_parser/option_section.h"
+#include "mongo/util/mongoutils/str.h"
using namespace mongo;
@@ -85,9 +89,7 @@ class Dump : public Tool {
ProgressMeter* _m;
};
- void doCollection( const string coll , FILE* out , ProgressMeter *m ) {
- Query q = _query;
-
+ void doCollection( const string coll , Query q, FILE* out , ProgressMeter *m ) {
int queryOptions = QueryOption_SlaveOk | QueryOption_NoCursorTimeout;
if (startsWith(coll.c_str(), "local.oplog."))
queryOptions |= QueryOption_OplogReplay;
@@ -113,7 +115,7 @@ class Dump : public Tool {
}
}
- void writeCollectionFile( const string coll , boost::filesystem::path outputFile ) {
+ void writeCollectionFile( const string coll , Query q, boost::filesystem::path outputFile ) {
toolInfoLog() << "\t" << coll << " to " << outputFile.string() << std::endl;
FilePtr f (fopen(outputFile.string().c_str(), "wb"));
@@ -123,7 +125,7 @@ class Dump : public Tool {
m.setName("Collection File Writing Progress");
m.setUnits("objects");
- doCollection(coll, f, &m);
+ doCollection(coll, q, f, &m);
toolInfoLog() << "\t\t " << m.done() << " objects" << std::endl;
}
@@ -163,12 +165,16 @@ class Dump : public Tool {
void writeCollectionStdout( const string coll ) {
- doCollection(coll, stdout, NULL);
+ doCollection(coll, _query, stdout, NULL);
}
- void go( const string db , const boost::filesystem::path outdir ) {
- toolInfoLog() << "DATABASE: " << db << "\t to \t" << outdir.string() << std::endl;
-
+ void go(const string& db,
+ const string& coll,
+ const Query& query,
+ const boost::filesystem::path& outdir,
+ const string& outFilename) {
+ // Can only provide outFilename if db and coll are provided
+ fassert(17368, outFilename.empty() || (!coll.empty() && !db.empty()));
boost::filesystem::create_directories( outdir );
map <string, BSONObj> collectionOptions;
@@ -194,7 +200,7 @@ class Dump : public Tool {
}
// skip namespaces with $ in them only if we don't specify a collection to dump
- if (toolGlobalParams.coll == "" && name.find(".$") != string::npos) {
+ if (coll == "" && name.find(".$") != string::npos) {
if (logger::globalLogDomain()->shouldLog(logger::LogSeverity::Debug(1))) {
toolInfoLog() << "\tskipping collection: " << name << std::endl;
}
@@ -204,9 +210,7 @@ class Dump : public Tool {
const string filename = name.substr( db.size() + 1 );
//if a particular collections is specified, and it's not this one, skip it
- if (toolGlobalParams.coll != "" &&
- db + "." + toolGlobalParams.coll != name &&
- toolGlobalParams.coll != name) {
+ if (coll != "" && db + "." + coll != name && coll != name) {
continue;
}
@@ -221,18 +225,23 @@ class Dump : public Tool {
if (nsToCollectionSubstring(name) == "system.indexes") {
// Create system.indexes.bson for compatibility with pre 2.2 mongorestore
const string filename = name.substr( db.size() + 1 );
- writeCollectionFile( name.c_str() , outdir / ( filename + ".bson" ) );
+ writeCollectionFile( name.c_str() , query, outdir / ( filename + ".bson" ) );
// Don't dump indexes as *.metadata.json
continue;
}
-
+
+ if (nsToCollectionSubstring(name) == "system.users" &&
+ !mongoDumpGlobalParams.dumpUsersAndRoles) {
+ continue;
+ }
+
collections.push_back(name);
}
for (vector<string>::iterator it = collections.begin(); it != collections.end(); ++it) {
string name = *it;
- const string filename = name.substr( db.size() + 1 );
- writeCollectionFile( name , outdir / ( filename + ".bson" ) );
+ const string filename = outFilename != "" ? outFilename : name.substr( db.size() + 1 );
+ writeCollectionFile( name , query, outdir / ( filename + ".bson" ) );
writeMetadataFile( name, outdir / (filename + ".metadata.json"), collectionOptions, indexes);
}
@@ -429,6 +438,17 @@ class Dump : public Tool {
}
}
+ if (mongoDumpGlobalParams.dumpUsersAndRoles) {
+ uassertStatusOK(auth::getRemoteStoredAuthorizationVersion(&conn(true),
+ &_serverAuthzVersion));
+ uassert(17369,
+ mongoutils::str::stream() << "Backing up users and roles is only supported for "
+ "clusters with auth schema versions 1 or 3, found: " <<
+ _serverAuthzVersion,
+ _serverAuthzVersion == AuthorizationManager::schemaVersion24 ||
+ _serverAuthzVersion == AuthorizationManager::schemaVersion26Final);
+ }
+
string opLogName = "";
unsigned long long opLogStart = 0;
if (mongoDumpGlobalParams.useOplog) {
@@ -508,11 +528,26 @@ class Dump : public Tool {
if ( (string)dbName == "local" )
continue;
- go ( dbName , root / dbName );
+ boost::filesystem::path outdir = root / dbName;
+ toolInfoLog() << "DATABASE: " << dbName << "\t to \t" << outdir.string()
+ << std::endl;
+ go ( dbName , "", _query, outdir, "" );
}
}
else {
- go(toolGlobalParams.db, root / toolGlobalParams.db);
+ boost::filesystem::path outdir = root / toolGlobalParams.db;
+ toolInfoLog() << "DATABASE: " << toolGlobalParams.db << "\t to \t" << outdir.string()
+ << std::endl;
+ go(toolGlobalParams.db, toolGlobalParams.coll, _query, outdir, "");
+ if (mongoDumpGlobalParams.dumpUsersAndRoles &&
+ _serverAuthzVersion == AuthorizationManager::schemaVersion26Final &&
+ toolGlobalParams.db != "admin") {
+ toolInfoLog() << "Backing up user and role data for the " << toolGlobalParams.db <<
+ " database";
+ Query query = Query(BSON("db" << toolGlobalParams.db));
+ go("admin", "system.users", query, outdir, "$admin.system.users");
+ go("admin", "system.roles", query, outdir, "$admin.system.roles");
+ }
}
if (!opLogName.empty()) {
@@ -521,13 +556,14 @@ class Dump : public Tool {
_query = BSON("ts" << b.obj());
- writeCollectionFile( opLogName , root / "oplog.bson" );
+ writeCollectionFile( opLogName , _query, root / "oplog.bson" );
}
return 0;
}
bool _usingMongos;
+ int _serverAuthzVersion;
BSONObj _query;
};
View
16 src/mongo/tools/mongodump_options.cpp
@@ -72,6 +72,9 @@ namespace mongo {
options->addOptionChaining("forceTableScan", "forceTableScan", moe::Switch,
"force a table scan (do not use $snapshot)");
+ options->addOptionChaining("dumpDbUsersAndRoles", "dumpDbUsersAndRoles", moe::Switch,
+ "Dump user and role definitions for the given database")
+ .requires("db").incompatibleWith("collection");
return Status::OK();
}
@@ -129,6 +132,19 @@ namespace mongo {
toolGlobalParams.db = "";
}
+ if (hasParam("dumpDbUsersAndRoles") && toolGlobalParams.db == "admin") {
+ return Status(ErrorCodes::BadValue,
+ "Cannot provide --dumpDbUsersAndRoles when dumping the admin db as "
+ "user and role definitions for the whole server are dumped by default "
+ "when dumping the admin db");
+ }
+
+ // Always dump users and roles if doing a full dump. If doing a db dump, only dump users
+ // and roles if --dumpDbUsersAndRoles provided or you're dumping the admin db.
+ mongoDumpGlobalParams.dumpUsersAndRoles = hasParam("dumpDbUsersAndRoles") ||
+ (toolGlobalParams.db.empty() && toolGlobalParams.coll.empty()) ||
+ (toolGlobalParams.db == "admin" && toolGlobalParams.coll.empty());
+
if (mongoDumpGlobalParams.outputDirectory == "-") {
// write output to standard error to avoid mangling output
// must happen early to avoid sending junk to stdout
View
1 src/mongo/tools/mongodump_options.h
@@ -43,6 +43,7 @@ namespace mongo {
bool useOplog;
bool repair;
bool snapShotQuery;
+ bool dumpUsersAndRoles;
};
extern MongoDumpGlobalParams mongoDumpGlobalParams;
View
18 src/mongo/tools/mongorestore_options.cpp
@@ -81,6 +81,10 @@ namespace mongo {
options->addOptionChaining("noIndexRestore", "noIndexRestore", moe::Switch,
"don't restore indexes");
+ options->addOptionChaining("restoreDbUsersAndRoles", "restoreDbUsersAndRoles", moe::Switch,
+ "Restore user and role definitions for the given database")
+ .requires("db").incompatibleWith("collection");
+
options->addOptionChaining("w", "w", moe::Int, "minimum number of replicas per write")
.setDefault(moe::Value(0));
@@ -144,6 +148,20 @@ namespace mongo {
toolGlobalParams.db = "";
}
+ if (hasParam("restoreDbUsersAndRoles") && toolGlobalParams.db == "admin") {
+ return Status(ErrorCodes::BadValue,
+ "Cannot provide --restoreDbUsersAndRoles when restoring the admin db as "
+ "user and role definitions for the whole server are restored by "
+ "default (if present) when restoring the admin db");
+ }
+
+ // Always restore users and roles if doing a full restore. If doing a db restore, only
+ // restore users and roles if --restoreDbUsersAndRoles provided or you're restoring the
+ // admin db
+ mongoRestoreGlobalParams.restoreUsersAndRoles = hasParam("restoreDbUsersAndRoles") ||
+ (toolGlobalParams.db.empty() && toolGlobalParams.coll.empty()) ||
+ (toolGlobalParams.db == "admin" && toolGlobalParams.coll.empty());
+
return Status::OK();
}
View
1 src/mongo/tools/mongorestore_options.h
@@ -44,6 +44,7 @@ namespace mongo {
bool keepIndexVersion;
bool restoreOptions;
bool restoreIndexes;
+ bool restoreUsersAndRoles;
int w;
std::string restoreDirectory;
};
View
337 src/mongo/tools/restore.cpp
@@ -36,7 +36,12 @@
#include <fstream>
#include <set>
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/client/auth_helpers.h"
#include "mongo/client/dbclientcursor.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/db/auth/role_name.h"
#include "mongo/db/json.h"
#include "mongo/db/namespace_string.h"
#include "mongo/tools/mongorestore_options.h"
@@ -57,11 +62,15 @@ class Restore : public BSONTool {
string _curns;
string _curdb;
string _curcoll;
- set<string> _users; // For restoring users with --drop
+ string _userDBFieldName;
+ set<UserName> _users; // Holds users that are already in the cluster when restoring with --drop
+ set<RoleName> _roles; // Holds roles that are already in the cluster when restoring with --drop
scoped_ptr<Matcher> _opmatcher; // For oplog replay
scoped_ptr<OpTime> _oplogLimitTS; // for oplog replay (limit)
int _oplogEntrySkips; // oplog entries skipped
int _oplogEntryApplies; // oplog entries applied
+ int _serverAuthzVersion; // authSchemaVersion of the cluster being restored into.
+ int _dumpFileAuthzVersion; // version extracted from admin.system.version file in dump.
Restore() : BSONTool() { }
virtual void printHelp(ostream& out) {
@@ -82,6 +91,64 @@ class Restore : public BSONTool {
return -1;
}
+ if (mongoRestoreGlobalParams.restoreUsersAndRoles) {
+ Status status = auth::getRemoteStoredAuthorizationVersion(&conn(),
+ &_serverAuthzVersion);
+ uassertStatusOK(status);
+ uassert(17370,
+ mongoutils::str::stream() << "Restoring users and roles is only supported for "
+ "clusters with auth schema versions " <<
+ AuthorizationManager::schemaVersion24 << " or " <<
+ AuthorizationManager::schemaVersion26Final << ", found: " <<
+ _serverAuthzVersion,
+ _serverAuthzVersion == AuthorizationManager::schemaVersion24 ||
+ _serverAuthzVersion == AuthorizationManager::schemaVersion26Final);
+
+ // Now that we know the schema version of the server we can pick whether to use
+ // "userSource" or "db" to identify users.
+ _userDBFieldName = _serverAuthzVersion == AuthorizationManager::schemaVersion26Final ?
+ "db" : "userSource";
+
+ if (toolGlobalParams.db.empty() && toolGlobalParams.coll.empty() &&
+ exists(root / "admin" / "system.version.bson")) {
+ // Will populate _dumpFileAuthzVersion
+ processFileAndMetadata(root / "admin" / "system.version.bson",
+ "admin.system.version");
+ uassert(17371,
+ mongoutils::str::stream() << "Server's authorization data schema version "
+ "does not match that of the data in the dump file. Server's schema"
+ " version: " << _serverAuthzVersion << ", schema version in dump: "
+ << _dumpFileAuthzVersion,
+ _serverAuthzVersion == _dumpFileAuthzVersion);
+ } else if (!toolGlobalParams.db.empty()) {
+ // DB-specific restore
+ if (exists(root / "$admin.system.users.bson")) {
+ uassert(17372,
+ mongoutils::str::stream() << "$admin.system.users.bson file found, "
+ "which implies that the dump was taken from a system with "
+ "schema version " << AuthorizationManager::schemaVersion26Final
+ << " users, but server has authorization schema version "
+ << _serverAuthzVersion,
+ _serverAuthzVersion == AuthorizationManager::schemaVersion26Final);
+ toolInfoLog() << "Restoring users for the " << toolGlobalParams.db <<
+ " database to admin.system.users" << endl;
+ processFileAndMetadata(root / "$admin.system.users.bson", "admin.system.users");
+ }
+ if (exists(root / "$admin.system.roles.bson")) {
+ uassert(17373,
+ mongoutils::str::stream() << "$admin.system.roles.bson file found, "
+ "which implies that the dump was taken from a system with "
+ "schema version " << AuthorizationManager::schemaVersion26Final
+ << " authorization data, but server has authorization schema "
+ "version " << _serverAuthzVersion,
+ _serverAuthzVersion == AuthorizationManager::schemaVersion26Final);
+ toolInfoLog() << "Restoring roles for the " << toolGlobalParams.db <<
+ " database to admin.system.roles" << endl;
+ processFileAndMetadata(root / "$admin.system.roles.bson", "admin.system.roles");
+ }
+ }
+ }
+
if (mongoRestoreGlobalParams.oplogReplay) {
// fail early if errors
@@ -268,22 +335,11 @@ class Restore : public BSONTool {
return;
}
- if ( endsWith( root.string().c_str() , ".metadata.json" ) ) {
- // Metadata files are handled when the corresponding .bson file is handled
- return;
- }
-
- if ( ! ( endsWith( root.string().c_str() , ".bson" ) ||
- endsWith( root.string().c_str() , ".bin" ) ) ) {
- toolError() << "don't know what to do with file [" << root.string() << "]" << std::endl;
- return;
- }
-
- toolInfoLog() << root.string() << std::endl;
-
- if ( root.leaf() == "system.profile.bson" ) {
- toolInfoLog() << "\t skipping system.profile.bson" << std::endl;
- return;
+ if (oplogReplayLimit) {
+ toolError() << "The oplogLimit option cannot be used if "
+ << "normal databases/collections exist in the dump directory."
+ << std::endl;
+ exit(EXIT_FAILURE);
}
string ns;
@@ -307,32 +363,107 @@ class Restore : public BSONTool {
ns += "." + oldCollName;
}
- if (oplogReplayLimit) {
- toolError() << "The oplogLimit option cannot be used if "
- << "normal databases/collections exist in the dump directory."
- << std::endl;
- exit(EXIT_FAILURE);
+ if ( endsWith( root.string().c_str() , ".metadata.json" ) ) {
+ // Metadata files are handled when the corresponding .bson file is handled
+ return;
+ }
+
+ if ((root.leaf() == "system.version.bson" && toolGlobalParams.db.empty()) ||
+ root.leaf() == "$admin.system.users.bson" ||
+ root.leaf() == "$admin.system.roles.bson") {
+ // These files were already explicitly handled at the beginning of the restore.
+ return;
+ }
+
+ if ( ! ( endsWith( root.string().c_str() , ".bson" ) ||
+ endsWith( root.string().c_str() , ".bin" ) ) ) {
+ toolError() << "don't know what to do with file [" << root.string() << "]" << std::endl;
+ return;
}
- toolInfoLog() << "\tgoing into namespace [" << ns << "]" << std::endl;
+ toolInfoLog() << root.string() << std::endl;
+ if ( root.leaf() == "system.profile.bson" ) {
+ toolInfoLog() << "\t skipping system.profile.bson" << std::endl;
+ return;
+ }
+
+ processFileAndMetadata(root, ns);
+ }
+
+ /**
+ * 1) Drop collection if --drop was specified. For system.users or system.roles collections,
+ * however, you don't want to remove all the users/roles up front as some of them may be needed
+ * by the restore. Instead, keep a set of all the users/roles originally in the server, then
+ * after restoring the users/roles from the dump, remove any users roles that were present in
+ * the system originally but aren't in the dump.
+ *
+ * 2) Parse metadata file (if present) and if the collection doesn't exist (or was just dropped
+ * b/c we're using --drop), create the collection with the options from the metadata file
+ *
+ * 3) Restore the data from the dump file for this collection
+ *
+ * 4) If the user asked to drop this collection, then at this point the _users and _roles sets
+ * will contain users and roles that were in the collection but not in the dump we are
+ * restoring. Iterate these sets and delete any users and roles that are there.
+ *
+ * 5) Restore indexes based on index definitions from the metadata file.
+ */
+ void processFileAndMetadata(const boost::filesystem::path& root, const std::string& ns) {
+
+ _curns = ns;
+ _curdb = nsToDatabase(_curns);
+ _curcoll = nsToCollectionSubstring(_curns).toString();
+
+ toolInfoLog() << "\tgoing into namespace [" << _curns << "]" << std::endl;
+
+ // 1) Drop collection if needed. Save user and role data if this is a system.users or
+ // system.roles collection
if (mongoRestoreGlobalParams.drop) {
- if (root.leaf() != "system.users.bson" ) {
- toolInfoLog() << "\t dropping" << std::endl;
- conn().dropCollection( ns );
- } else {
+ if (_curcoll == "system.users") {
// Create map of the users currently in the DB
- BSONObj fields = BSON("user" << 1);
- scoped_ptr<DBClientCursor> cursor(conn().query(ns, Query(), 0, 0, &fields));
+ BSONObj fields = BSON("user" << 1 << _userDBFieldName << 1);
+ scoped_ptr<DBClientCursor> cursor(conn().query(_curns, Query(), 0, 0, &fields));
while (cursor->more()) {
BSONObj user = cursor->next();
- _users.insert(user["user"].String());
+ string userDB;
+ uassertStatusOK(bsonExtractStringFieldWithDefault(user,
+ _userDBFieldName,
+ _curdb,
+ &userDB));
+ _users.insert(UserName(user["user"].String(), userDB));
}
}
+ else if (_curns == "admin.system.roles") {
+ // Create map of the roles currently in the DB
+ BSONObj fields = BSON("role" << 1 << "db" << 1);
+ scoped_ptr<DBClientCursor> cursor(conn().query(_curns, Query(), 0, 0, &fields));
+ while (cursor->more()) {
+ BSONObj role = cursor->next();
+ _roles.insert(RoleName(role["role"].String(), role["db"].String()));
+ }
+ }
+ else {
+ toolInfoLog() << "\t dropping" << std::endl;
+ conn().dropCollection( ns );
+ }
+ } else {
+ // If drop is not used, warn if the collection exists.
+ scoped_ptr<DBClientCursor> cursor(conn().query(_curdb + ".system.namespaces",
+ Query(BSON("name" << ns))));
+ if (cursor->more()) {
+ // collection already exists show warning
+ toolError() << "Restoring to " << ns << " without dropping. Restored data "
+ << "will be inserted without raising errors; check your server log"
+ << std::endl;
+ }
}
+ // 2) Create collection with options from metadata file if present
BSONObj metadataObject;
if (mongoRestoreGlobalParams.restoreOptions || mongoRestoreGlobalParams.restoreIndexes) {
+ string oldCollName = root.leaf().string(); // Name of collection that was dumped from
+ oldCollName = oldCollName.substr( 0 , oldCollName.find_last_of( "." ) );
boost::filesystem::path metadataFile = (root.branch_path() / (oldCollName + ".metadata.json"));
if (!boost::filesystem::exists(metadataFile.string())) {
// This is fine because dumps from before 2.1 won't have a metadata file, just print a warning.
@@ -345,37 +476,50 @@ class Restore : public BSONTool {
}
}
- _curns = ns.c_str();
- _curdb = nsToDatabase(_curns);
- _curcoll = nsToCollectionSubstring(_curns).toString();
-
- // If drop is not used, warn if the collection exists.
- if (!mongoRestoreGlobalParams.drop) {
- scoped_ptr<DBClientCursor> cursor(conn().query(_curdb + ".system.namespaces",
- Query(BSON("name" << ns))));
- if (cursor->more()) {
- // collection already exists show warning
- toolError() << "Restoring to " << ns << " without dropping. Restored data "
- << "will be inserted without raising errors; check your server log"
- << std::endl;
- }
- }
-
if (mongoRestoreGlobalParams.restoreOptions && metadataObject.hasField("options")) {
// Try to create collection with given options
createCollectionWithOptions(metadataObject["options"].Obj());
}
+ // 3) Actually restore the BSONObjs inside the dump file
processFile( root );
- if (mongoRestoreGlobalParams.drop && root.leaf() == "system.users.bson") {
+
+ // 4) If running with --drop, remove any users/roles that were in the system at the
+ // beginning of the restore but weren't found in the dump file
+ if (mongoRestoreGlobalParams.drop && _curcoll == "system.users") {
// Delete any users that used to exist but weren't in the dump file
- for (set<string>::iterator it = _users.begin(); it != _users.end(); ++it) {
- BSONObj userMatch = BSON("user" << *it);
- conn().remove(ns, Query(userMatch));
+ for (set<UserName>::iterator it = _users.begin(); it != _users.end(); ++it) {
+ const UserName& name = *it;
+ string dbFieldName = _userDBFieldName;
+ if (_curdb != "admin") {
+ // Always use userSource when restoring to the legacy system.users collections
+ // found in non-admin databases, even if the system is otherwise upgrade to v3.
+ dbFieldName = "userSource";
+ }
+
+ BSONObjBuilder queryBuilder;
+ queryBuilder << "user" << name.getUser();
+ if (dbFieldName == "userSource" && name.getDB() == _curdb) {
+ // userSource field won't be present for v1 users docs in the same db as the
+ // user is defined on.
+ queryBuilder << "userSource" << BSONNULL;
+ } else {
+ queryBuilder << dbFieldName << name.getDB();
+ }
+ conn().remove(_curns, Query(queryBuilder.done()));
}
_users.clear();
}
+ if (mongoRestoreGlobalParams.drop && _curns == "admin.system.roles") {
+ // Delete any roles that used to exist but weren't in the dump file
+ for (set<RoleName>::iterator it = _roles.begin(); it != _roles.end(); ++it) {
+ const RoleName& name = *it;
+ conn().remove(ns, Query(BSON("role" << name.getRole() << "db" << name.getDB())));
+ }
+ _roles.clear();
+ }
+ // 5) Restore indexes
if (mongoRestoreGlobalParams.restoreIndexes && metadataObject.hasField("indexes")) {
vector<BSONElement> indexes = metadataObject["indexes"].Array();
for (vector<BSONElement>::iterator it = indexes.begin(); it != indexes.end(); ++it) {
@@ -410,28 +554,93 @@ class Restore : public BSONTool {
toolError() << "Error while replaying oplog: " << err << std::endl;
}
}
+ return;
}
- else if (nsToCollectionSubstring(_curns) == "system.indexes") {
+
+ if (nsToCollectionSubstring(_curns) == "system.indexes") {
createIndex(obj, true);
}
else if (mongoRestoreGlobalParams.drop &&
- nsToCollectionSubstring(_curns) == ".system.users" &&
- _users.count(obj["user"].String())) {
+ _curns == "admin.system.roles" &&
+ _roles.count(RoleName(obj["role"].String(), obj["db"].String()))) {
// Since system collections can't be dropped, we have to manually
- // replace the contents of the system.users collection
- BSONObj userMatch = BSON("user" << obj["user"].String());
- conn().update(_curns, Query(userMatch), obj);
- _users.erase(obj["user"].String());
+ // replace the contents of the system.roles collection
+ BSONObj roleMatch = BSON("role" << obj["role"].String() << "db" << obj["db"].String());
+ conn().update(_curns, Query(roleMatch), obj);
+ _roles.erase(RoleName(obj["role"].String(), obj["db"].String()));
+ }
+ else if (_curcoll == "system.users") {
+ string userDB;
+ uassertStatusOK(bsonExtractStringFieldWithDefault(obj,
+ _userDBFieldName,
+ _curdb,
+ &userDB));
+
+ if (_curdb == "admin" && obj.hasField("credentials")) { // Treat non-admin db like 2.4
+ if (_serverAuthzVersion == AuthorizationManager::schemaVersion24) {
+ // v3 user, v1 system
+ toolError() << "Server has authorization schema version " <<
+ AuthorizationManager::schemaVersion24 << ", but found a schema "
+ "version " << AuthorizationManager::schemaVersion26Final << " user: " <<
+ obj.toString() << endl;
+ exit(EXIT_FAILURE);
+ } else {
+ // v3 user, v3 system
+ if (mongoRestoreGlobalParams.drop && _users.count(UserName(obj["user"].String(),
+ userDB))) {
+ // Since system collections can't be dropped, we have to manually
+ // replace the contents of the system.users collection
+ BSONObj userMatch = BSON("user" << obj["user"].String() << "db" << userDB);
+ conn().update(_curns, Query(userMatch), obj);
+ _users.erase(UserName(obj["user"].String(), userDB));
+ } else {
+ conn().insert(_curns, obj);
+ }
+ }
+ } else {
+ if (_serverAuthzVersion == AuthorizationManager::schemaVersion24 ||
+ _curdb != "admin") { // Restoring 2.4 schema users to non-admin dbs is OK
+ // v1 user, v1 system
+ if (mongoRestoreGlobalParams.drop && _users.count(UserName(obj["user"].String(),
+ userDB))) {
+ // Since system collections can't be dropped, we have to manually
+ // replace the contents of the system.users collection
+ BSONObj userMatch = BSON("user" << obj["user"].String() <<
+ "userSource" << userDB);
+ conn().update(_curns, Query(userMatch), obj);
+ _users.erase(UserName(obj["user"].String(), userDB));
+ } else {
+ conn().insert(_curns, obj);
+ }
+ } else {
+ // v1 user, v3 system
+ // TODO(spencer): SERVER-12491 Rather than failing here, we should convert the
+ // v1 user to an equivalent v3 schema user
+ toolError() << "Server has authorization schema version " <<
+ AuthorizationManager::schemaVersion26Final << ", but found a schema "
+ "version " << AuthorizationManager::schemaVersion24 << " user: " <<
+ obj.toString() << endl;
+ exit(EXIT_FAILURE);
+ }
+ }
}
else {
- conn().insert( _curns , obj );
+ if (_curns == "admin.system.version") {
+ long long authVersion;
+ uassertStatusOK(bsonExtractIntegerField(obj,
+ AuthorizationManager::schemaVersionFieldName,
+ &authVersion));
+ _dumpFileAuthzVersion = static_cast<int>(authVersion);
+ }
+ conn().insert(_curns, obj);
+ }
- // wait for insert to propagate to "w" nodes (doesn't warn if w used without replset)
- if (mongoRestoreGlobalParams.w > 0) {
- string err = conn().getLastError(_curdb, false, false, mongoRestoreGlobalParams.w);
- if (!err.empty()) {
- toolError() << err << std::endl;
- }
+ // wait for insert (or update) to propagate to "w" nodes (doesn't warn if w used
+ // without replset)
+ if (mongoRestoreGlobalParams.w > 0) {
+ string err = conn().getLastError(_curdb, false, false, mongoRestoreGlobalParams.w);
+ if (!err.empty()) {
+ toolError() << err << std::endl;
}
}
}

0 comments on commit dac6264

Please sign in to comment.