diff --git a/mongorestore/dumprestore_auth_test.go b/mongorestore/dumprestore_auth_test.go index 5e6267bd7..b5c765601 100644 --- a/mongorestore/dumprestore_auth_test.go +++ b/mongorestore/dumprestore_auth_test.go @@ -908,6 +908,73 @@ func dumpRestoreSingleDBWithUsersAndRoles(t *testing.T) { }) } +// TestRestoreWithDBUserPreservesIndexes covers the parts of the legacy +// restorewithauth.js (SERVER-4972) that TestDumpRestoreEnforcesAuthRoles does +// not: restoring as a non-admin, per-database user, and confirming that +// secondary indexes survive the round trip. A collection with an extra index +// ends up with both _id and that index, while a collection with only _id keeps +// just _id. +func TestRestoreWithDBUserPreservesIndexes(t *testing.T) { + testtype.SkipUnlessTestType(t, testtype.IntegrationTestType) + testtype.SkipUnlessTestType(t, testtype.AuthTestType) + + const ( + dbName = "testdr_restorewithauth" + dbUser = "restorewithauth_user" + ) + + adminClient, err := testutil.GetBareSession() + require.NoError(t, err) + + adminDB := adminClient.Database("admin") + testDB := adminClient.Database(dbName) + barColl := testDB.Collection("bar") + bazColl := testDB.Collection("baz") + + cleanup := func() { + silentDropUser(adminDB, dbUser) + _ = testDB.Drop(context.Background()) + } + cleanup() + t.Cleanup(cleanup) + + ctx := context.Background() + for i := 0; i < 4; i++ { + _, err = barColl.InsertOne(ctx, bson.D{{Key: "x", Value: i}}) + require.NoError(t, err) + _, err = bazColl.InsertOne(ctx, bson.D{{Key: "x", Value: i}}) + require.NoError(t, err) + } + _, err = barColl.Indexes(). + CreateOne(ctx, mongo.IndexModel{Keys: bson.D{{Key: "x", Value: 1}}}) + require.NoError(t, err, "creating secondary index on bar") + + // readWrite includes the createCollection/createIndex/insert privileges that + // mongorestore needs to restore data and indexes into a single database. + mustCreateUser(t, adminDB, dbUser, "password", bson.A{dbRole("readWrite", dbName)}) + + dumpDir, cleanDump := testutil.MakeTempDir(t) + defer cleanDump() + + dumpOpts := baseToolOpts(t) + dumpOpts.Namespace = &options.Namespace{DB: dbName} + require.NoError(t, runDump(t, dumpOpts, dumpDir, nil), "dump db as admin") + + require.NoError(t, testDB.Drop(ctx), "dropping db before restore") + + restoreOpts := toolOptsForUser(t, dbUser, "password") + require.NoError( + t, + runRestore(t, restoreOpts, dumpDir, nil), + "restore as a non-admin per-db user", + ) + + assert.Equal(t, int64(4), docCount(t, barColl), "bar restored with 4 documents") + assert.Equal(t, int64(4), docCount(t, bazColl), "baz restored with 4 documents") + assert.Equal(t, 2, countIndexes(t, barColl), "bar has _id and x indexes after restore") + assert.Equal(t, 1, countIndexes(t, bazColl), "baz has only its _id index after restore") +} + // baseToolOpts returns a fresh set of tool options from the environment. func baseToolOpts(t *testing.T) *options.ToolOptions { t.Helper() @@ -1025,6 +1092,16 @@ func docCount(t *testing.T, coll *mongo.Collection) int64 { return count } +// countIndexes returns the number of indexes on the collection. +func countIndexes(t *testing.T, coll *mongo.Collection) int { + t.Helper() + cursor, err := coll.Indexes().List(context.Background()) + require.NoError(t, err, "listing indexes") + var indexes []bson.D + require.NoError(t, cursor.All(context.Background(), &indexes), "reading index list") + return len(indexes) +} + // mustCreateUser runs a createUser command on db, failing the test on error. func mustCreateUser(t *testing.T, db *mongo.Database, user, password string, roles bson.A) { t.Helper() diff --git a/test/legacy42/jstests/tool/restorewithauth.js b/test/legacy42/jstests/tool/restorewithauth.js deleted file mode 100644 index 20cbad57b..000000000 --- a/test/legacy42/jstests/tool/restorewithauth.js +++ /dev/null @@ -1,131 +0,0 @@ -/* SERVER-4972 - * Test for mongorestore on server with --auth allows restore without credentials of colls - * with no index - */ -/* - * 1) Start mongo without auth. - * 2) Write to collection - * 3) Take dump of the collection using mongodump. - * 4) Drop the collection. - * 5) Stop mongod from step 1. - * 6) Restart mongod with auth. - * 7) Add admin user to kick authentication - * 8) Try restore without auth credentials. The restore should fail - * 9) Try restore with correct auth credentials. The restore should succeed this time. - */ - -baseName = "jstests_restorewithauth"; -var conn = MongoRunner.runMongod({bind_ip: "127.0.0.1"}); - -// write to ns foo.bar -var foo = conn.getDB("foo"); -for (var i = 0; i < 4; i++) { - foo["bar"].save({"x": i}); - foo["baz"].save({"x": i}); -} - -// make sure the collection exists -var collNames = foo.getCollectionNames(); -assert.neq(-1, collNames.indexOf("bar"), "bar collection doesn't exist"); - -// make sure it has no index except _id -assert.eq(foo.bar.getIndexes().length, 1); -assert.eq(foo.baz.getIndexes().length, 1); - -foo.bar.createIndex({x: 1}); -assert.eq(foo.bar.getIndexes().length, 2); -assert.eq(foo.baz.getIndexes().length, 1); - -// get data dump -var dumpdir = MongoRunner.dataDir + "/restorewithauth-dump1/"; -resetDbpath(dumpdir); - -var exitCode = MongoRunner.runMongoTool("mongodump", { - db: "foo", - host: "127.0.0.1:" + conn.port, - out: dumpdir, -}); -assert.eq(0, exitCode, "mongodump failed to dump data from mongod without auth enabled"); - -// now drop the db -foo.dropDatabase(); - -// stop mongod -MongoRunner.stopMongod(conn); - -// start mongod with --auth -conn = MongoRunner.runMongod({auth: "", bind_ip: "127.0.0.1"}); - -// admin user -var admin = conn.getDB("admin"); -admin.createUser({user: "admin", pwd: "admin", roles: jsTest.adminUserRoles}); -admin.auth("admin", "admin"); - -var foo = conn.getDB("foo"); - -// make sure no collection with the same name exists -collNames = foo.getCollectionNames(); -assert.eq(-1, collNames.indexOf("bar"), "bar collection already exists"); -assert.eq(-1, collNames.indexOf("baz"), "baz collection already exists"); - -// now try to restore dump -exitCode = MongoRunner.runMongoTool("mongorestore", { - host: "127.0.0.1:" + conn.port, - dir: dumpdir, - verbose: 5, -}); -assert.neq(0, - exitCode, - "mongorestore shouldn't have been able to restore data to mongod with auth enabled"); - -// make sure that the collection isn't restored -collNames = foo.getCollectionNames(); -assert.eq(-1, collNames.indexOf("bar"), "bar collection was restored"); -assert.eq(-1, collNames.indexOf("baz"), "baz collection was restored"); - -// now try to restore dump with correct credentials -exitCode = MongoRunner.runMongoTool("mongorestore", { - host: "127.0.0.1:" + conn.port, - db: "foo", - authenticationDatabase: "admin", - username: "admin", - password: "admin", - dir: dumpdir + "foo/", - verbose: 5, -}); -assert.eq(0, exitCode, "mongorestore failed to restore data to mongod with auth enabled"); - -// make sure that the collection was restored -collNames = foo.getCollectionNames(); -assert.neq(-1, collNames.indexOf("bar"), "bar collection was not restored"); -assert.neq(-1, collNames.indexOf("baz"), "baz collection was not restored"); - -// make sure the collection has 4 documents -assert.eq(foo.bar.count(), 4); -assert.eq(foo.baz.count(), 4); - -foo.dropDatabase(); - -foo.createUser({user: 'user', pwd: 'password', roles: jsTest.basicUserRoles}); - -// now try to restore dump with foo database credentials -exitCode = MongoRunner.runMongoTool("mongorestore", { - host: "127.0.0.1:" + conn.port, - db: "foo", - username: "user", - password: "password", - dir: dumpdir + "foo/", - verbose: 5, -}); -assert.eq(0, exitCode, "mongorestore failed to restore the 'foo' database"); - -// make sure that the collection was restored -collNames = foo.getCollectionNames(); -assert.neq(-1, collNames.indexOf("bar"), "bar collection was not restored"); -assert.neq(-1, collNames.indexOf("baz"), "baz collection was not restored"); -assert.eq(foo.bar.count(), 4); -assert.eq(foo.baz.count(), 4); -assert.eq(foo.bar.getIndexes().length + foo.baz.getIndexes().length, - 3); // _id on foo, _id on bar, x on foo - -MongoRunner.stopMongod(conn);