summaryrefslogtreecommitdiff
path: root/jstests/auth/drop-user-transaction.js
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2020-10-05 21:59:11 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-10-21 18:45:33 +0000
commit7a9eb6f3d26eb31c271434b47a0556046d6425a8 (patch)
tree6ef6920867d3c7472b3eeddad7bbd8115a69891c /jstests/auth/drop-user-transaction.js
parentea5193ab6baf48eaa69b0910fdb36a734277b3ad (diff)
downloadmongo-7a9eb6f3d26eb31c271434b47a0556046d6425a8.tar.gz
SERVER-49931 Transactionalize dropUser commands
Diffstat (limited to 'jstests/auth/drop-user-transaction.js')
-rw-r--r--jstests/auth/drop-user-transaction.js136
1 files changed, 136 insertions, 0 deletions
diff --git a/jstests/auth/drop-user-transaction.js b/jstests/auth/drop-user-transaction.js
new file mode 100644
index 00000000000..e0c09251528
--- /dev/null
+++ b/jstests/auth/drop-user-transaction.js
@@ -0,0 +1,136 @@
+// Validate dropUser performed via transaction.
+
+(function() {
+'use strict';
+
+function runTest(conn, testCB) {
+ const admin = conn.getDB('admin');
+ const test = conn.getDB('test');
+ admin.createUser({user: 'admin', pwd: 'pwd', roles: ['__system']});
+ admin.auth('admin', 'pwd');
+
+ // user1 -> role2 -> role1
+ // \___________.^
+ assert.commandWorked(test.runCommand({createRole: 'role1', roles: [], privileges: []}));
+ assert.commandWorked(test.runCommand({createRole: 'role2', roles: ['role1'], privileges: []}));
+ assert.commandWorked(
+ test.runCommand({createUser: 'user1', roles: ['role1', 'role2'], pwd: 'pwd'}));
+
+ const beforeDrop = assert.commandWorked(test.runCommand({usersInfo: 'user1'})).users[0].roles;
+ assert.eq(beforeDrop.length, 2);
+ assert.eq(beforeDrop.map((r) => r.role).sort(), ['role1', 'role2']);
+
+ testCB(test);
+
+ // Callback should end up dropping role1
+ // And we should have no references left to it.
+ const allUsers = assert.commandWorked(test.runCommand({usersInfo: 1})).users;
+ assert.eq(allUsers.length, 1);
+ assert.eq(allUsers[0]._id, 'test.user1');
+ assert.eq(allUsers[0].roles.map((r) => r.role), ['role2']);
+
+ const allRoles = assert.commandWorked(test.runCommand({rolesInfo: 1})).roles;
+ assert.eq(allRoles.length, 1);
+ assert.eq(allRoles[0]._id, 'test.role2');
+ assert.eq(allRoles[0].roles.length, 0);
+
+ admin.logout();
+}
+
+//// Standalone
+// We don't have transactions in standalone mode.
+// Behavior elides transaction machinery, but is still protected by
+// local mutex on the UMC commands.
+// Expect the second command to block.
+{
+ const kFailpointDelay = 10 * 1000;
+ const mongod = MongoRunner.runMongod({auth: null});
+ assert.commandWorked(mongod.getDB('admin').runCommand({
+ configureFailPoint: 'umcTransaction',
+ mode: 'alwaysOn',
+ data: {commitDelayMS: NumberInt(kFailpointDelay)},
+ }));
+
+ runTest(mongod, function(test) {
+ // Pause and cause next op to block.
+ const start = Date.now();
+ const parallelShell = startParallelShell(
+ `
+ db.getSiblingDB('admin').auth('admin', 'pwd');
+ assert.commandWorked(db.getSiblingDB('test').runCommand({dropRole: 'role1'}));
+ `,
+ mongod.port);
+
+ // Other UMCs block.
+ assert.commandWorked(test.runCommand({updateRole: 'role2', privileges: []}));
+ parallelShell();
+ assert.gte(Date.now() - start, kFailpointDelay);
+ });
+
+ MongoRunner.stopMongod(mongod);
+}
+
+//// ReplicaSet
+// Ensure that dropRoles generates a transaction by checking for applyOps.
+{
+ const rst = new ReplSetTest({nodes: 3, keyFile: 'jstests/libs/key1'});
+ rst.startSet();
+ rst.initiate();
+ rst.awaitSecondaryNodes();
+
+ function relevantOp(op) {
+ return ((op.op === 'u') || (op.op === 'd')) &&
+ ((op.ns === 'admin.system.users') || (op.ns === 'admin.system.roles'));
+ }
+
+ function probableTransaction(op) {
+ return (op.op === 'c') && (op.ns === 'admin.$cmd') && (op.o.applyOps !== undefined) &&
+ op.o.applyOps.some(relevantOp);
+ }
+
+ runTest(rst.getPrimary(), function(test) {
+ assert.commandWorked(test.runCommand({dropRole: 'role1'}));
+ const oplog = test.getSiblingDB('local').oplog.rs.find({}).toArray();
+ jsTest.log('Oplog: ' + tojson(oplog));
+
+ // Events were not executed directly on the collections.
+ const updatesAndDrops = oplog.filter(relevantOp);
+ assert.eq(updatesAndDrops.length,
+ 0,
+ 'Found expected actions on priv collections: ' + tojson(updatesAndDrops));
+
+ // They were executed by way of a transaction.
+ const txns = oplog.filter(probableTransaction);
+ assert.eq(
+ txns.length, 1, 'Found unexpected number of probable transactions: ' + tojson(txns));
+
+ const txnOps = txns[0].o.applyOps;
+ assert.eq(
+ txnOps.length, 3, 'Found unexpected number of ops in transaction: ' + tojson(txnOps));
+
+ // Op1: Remove 'role1' from user1
+ const msgUpdateUser = 'First op should be update admin.system.users' + tojson(txnOps);
+ assert.eq(txnOps[0].op, 'u', msgUpdateUser);
+ assert.eq(txnOps[0].ns, 'admin.system.users', msgUpdateUser);
+ assert.eq(txnOps[0].o2._id, 'test.user1', msgUpdateUser);
+ assert.eq(txnOps[0].o.diff.u.roles, [{role: 'role2', db: 'test'}], msgUpdateUser);
+
+ // Op2: Remove 'role1' from role2
+ const msgUpdateRole = 'Second op should be update admin.system.roles' + tojson(txnOps);
+ assert.eq(txnOps[1].op, 'u', msgUpdateRole);
+ assert.eq(txnOps[1].ns, 'admin.system.roles', msgUpdateRole);
+ assert.eq(txnOps[1].o2._id, 'test.role2', msgUpdateRole);
+ assert.eq(txnOps[1].o.diff.u.roles, [], msgUpdateRole);
+
+ // Op3: Remove 'role1' document
+ const msgDropRole = 'Third op should be drop from admin.system.roles' + tojson(txnOps);
+ assert.eq(txnOps[2].op, 'd', msgDropRole);
+ assert.eq(txnOps[2].ns, 'admin.system.roles', msgUpdateRole);
+ assert.eq(txnOps[2].o._id, 'test.role1', msgUpdateRole);
+
+ jsTest.log('Oplog applyOps: ' + tojson(txns));
+ });
+
+ rst.stopSet();
+}
+})();