mirror of https://github.com/mongodb/mongo
137 lines
5.3 KiB
JavaScript
137 lines
5.3 KiB
JavaScript
// Validate dropUser performed via transaction.
|
|
// @tags: [requires_replication,exclude_from_large_txns]
|
|
|
|
import {ReplSetTest} from "jstests/libs/replsettest.js";
|
|
|
|
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 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();
|
|
|
|
jsTest.log("Verify the failpoint is triggered.");
|
|
const kUMCTransactionCommitDelayLogId = 4993100;
|
|
checkLog.containsJson(mongod, kUMCTransactionCommitDelayLogId, {durationMillis: 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();
|
|
}
|