// Test the killCursors command. // @tags: [requires_sharding] (function() { 'use strict'; // TODO SERVER-35447: Multiple users cannot be authenticated on one connection within a session. TestData.disableImplicitSessions = true; function runTest(mongod) { /** * Open a cursor on `db` while authenticated as `authUsers`. * Then logout, and log back in as `killUsers` and try to kill that cursor. * * @param db - The db to create a cursor on and ultimately kill agains. * @param authUsers - Array of ['username', db] pairs to create the cursor under. * @param killUsers - Array of ['username', dn] pairs to use when killing. * @param shouldWork - Whether we expect success */ function tryKill(db, authUsers, killUsers, shouldWork) { function loginAll(users) { users.forEach(function(u) { assert(u[1].auth(u[0], 'pass')); }); } function logoutAll() { [testA, testB].forEach(function(d) { const users = assert.commandWorked(d.runCommand({connectionStatus: 1})) .authInfo.authenticatedUsers; users.forEach(function(u) { mongod.getDB(u.db).logout(); }); }); } function doKill(extra) { // Create a cursor to be killed later. loginAll(authUsers); let cmd = {find: db.coll.getName(), batchSize: 2}; Object.assign(cmd, extra); const id = assert.commandWorked(db.runCommand(cmd)).cursor.id; assert.neq(id, 0, "Invalid cursor ID"); logoutAll(); loginAll(killUsers); const killCmd = db.runCommand({killCursors: db.coll.getName(), cursors: [id]}); logoutAll(); if (shouldWork) { assert.commandWorked(killCmd, "Unable to kill cursor"); } else { assert.commandFailed(killCmd, "Should not have been able to kill cursor"); } } doKill({}); if ((authUsers.length === 1) && (killUsers.length === 1)) { // Session variant only makes sense with single auth'd users. doKill({lsid: {id: BinData(4, "QlLfPHTySm6tqfuV+EOsVA==")}}); } } function trySelfKill(user) { const db = user[1]; assert(db.auth(user[0], 'pass')); assert.commandWorked(db.runCommand({startSession: 1})); const cmd = {aggregate: 1, pipeline: [{$listLocalSessions: {}}], cursor: {batchSize: 0}}; const res = assert.commandWorked(db.runCommand(cmd)); print(tojson(res)); const id = res.cursor.id; assert.neq(id, 0, "Invalid cursor ID"); const killCmdRes = db.runCommand({killCursors: db.getName() + ".$cmd", cursors: [id]}); db.logout(); assert.commandWorked(killCmdRes, "Unable to kill cursor"); } /** * Create user1/user2 in testA, and user3/user4 in testB. * Create two 101 element collections in testA and testB. * Use various combinations of those users to open cursors, * then (potentially) different combinations of users to kill them. * * A cursor should only be killable if at least one of the users * who created it is trying to kill it. */ const testA = mongod.getDB('testA'); const testB = mongod.getDB('testB'); const admin = mongod.getDB('admin'); // Setup users admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles}); assert(admin.auth('admin', 'pass')); testA.createUser({user: 'user1', pwd: 'pass', roles: jsTest.basicUserRoles}); testA.createUser({user: 'user2', pwd: 'pass', roles: jsTest.basicUserRoles}); testB.createUser({user: 'user3', pwd: 'pass', roles: jsTest.basicUserRoles}); testB.createUser({user: 'user4', pwd: 'pass', roles: jsTest.basicUserRoles}); testB.createUser({user: 'user5', pwd: 'pass', roles: []}); admin.logout(); // Create a collection with batchable data assert(testA.auth('user1', 'pass')); assert(testB.auth('user3', 'pass')); for (var i = 0; i < 101; ++i) { assert.commandWorked(testA.coll.insert({_id: i})); assert.commandWorked(testB.coll.insert({_id: i})); } testA.logout(); testB.logout(); // A user can kill their own cursor. tryKill(testA, [['user1', testA]], [['user1', testA]], true); tryKill(testA, [['user2', testA]], [['user2', testA]], true); tryKill(testB, [['user3', testB]], [['user3', testB]], true); tryKill(testB, [['user4', testB]], [['user4', testB]], true); trySelfKill(['user1', testA]); trySelfKill(['user5', testB]); trySelfKill(['admin', admin]); // A user cannot kill someone else's cursor. tryKill(testA, [['user1', testA]], [['user2', testA]], false); tryKill(testA, [['user1', testA]], [['user2', testA], ['user3', testB]], false); tryKill(testA, [['user2', testA]], [['user1', testA]], false); tryKill(testA, [['user2', testA]], [['user1', testA], ['user3', testB]], false); tryKill(testB, [['user3', testB]], [['user1', testA], ['user4', testB]], false); tryKill(testB, [['user3', testB]], [['user2', testA], ['user4', testB]], false); // A multi-owned cursor can be killed by any/all owner. tryKill(testA, [['user1', testA], ['user3', testB]], [['user1', testA]], true); tryKill(testB, [['user1', testA], ['user3', testB]], [['user3', testB]], true); tryKill( testA, [['user1', testA], ['user3', testB]], [['user1', testA], ['user3', testB]], true); tryKill( testA, [['user1', testA], ['user3', testB]], [['user2', testA], ['user3', testB]], true); tryKill( testB, [['user1', testA], ['user3', testB]], [['user1', testA], ['user3', testB]], true); tryKill( testB, [['user1', testA], ['user3', testB]], [['user1', testA], ['user4', testB]], true); // An owned cursor can not be killed by other user(s). tryKill( testA, [['user1', testA], ['user3', testB]], [['user2', testA], ['user4', testB]], false); tryKill(testA, [['user1', testA]], [['user2', testA], ['user3', testB]], false); tryKill( testA, [['user1', testA], ['user3', testB]], [['user2', testA], ['user4', testB]], false); // Admin can kill anything. tryKill(testA, [['user1', testA]], [['admin', admin]], true); tryKill(testA, [['user2', testA]], [['admin', admin]], true); tryKill(testB, [['user3', testB]], [['admin', admin]], true); tryKill(testB, [['user4', testB]], [['admin', admin]], true); tryKill(testA, [['user1', testA], ['user3', testB]], [['admin', admin]], true); tryKill(testB, [['user2', testA], ['user4', testB]], [['admin', admin]], true); } const mongod = MongoRunner.runMongod({auth: ""}); runTest(mongod); MongoRunner.stopMongod(mongod); // TODO: Remove 'shardAsReplicaSet: false' when SERVER-32672 is fixed. const st = new ShardingTest({ shards: 1, mongos: 1, config: 1, other: {keyFile: 'jstests/libs/key1', shardAsReplicaSet: false} }); runTest(st.s0); st.stop(); })();