mirror of https://github.com/mongodb/mongo
545 lines
18 KiB
JavaScript
545 lines
18 KiB
JavaScript
/**
|
|
* This file tests basic authorization for different roles in stand alone and sharded
|
|
* environment. This file covers all types of operations except commands.
|
|
*/
|
|
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
|
|
/**
|
|
* Data structure that contains all the users that are going to be used in the tests.
|
|
* The structure is as follows:
|
|
* 1st level field name: database names.
|
|
* 2nd level field name: user names.
|
|
* 3rd level is an object that has the format:
|
|
* { pwd: <password>, roles: [<list of roles>] }
|
|
* @tags: [requires_sharding]
|
|
*/
|
|
let AUTH_INFO = {
|
|
admin: {
|
|
root: {pwd: "root", roles: ["root"]},
|
|
cluster: {pwd: "cluster", roles: ["clusterAdmin"]},
|
|
anone: {pwd: "none", roles: []},
|
|
aro: {pwd: "ro", roles: ["read"]},
|
|
arw: {pwd: "rw", roles: ["readWrite"]},
|
|
aadmin: {pwd: "admin", roles: ["dbAdmin"]},
|
|
auadmin: {pwd: "uadmin", roles: ["userAdmin"]},
|
|
any_ro: {pwd: "ro", roles: ["readAnyDatabase"]},
|
|
any_rw: {pwd: "rw", roles: ["readWriteAnyDatabase"]},
|
|
any_admin: {pwd: "admin", roles: ["dbAdminAnyDatabase"]},
|
|
any_uadmin: {pwd: "uadmin", roles: ["userAdminAnyDatabase"]},
|
|
},
|
|
test: {
|
|
none: {pwd: "none", roles: []},
|
|
ro: {pwd: "ro", roles: ["read"]},
|
|
rw: {pwd: "rw", roles: ["readWrite"]},
|
|
roadmin: {pwd: "roadmin", roles: ["read", "dbAdmin"]},
|
|
admin: {pwd: "admin", roles: ["dbAdmin"]},
|
|
uadmin: {pwd: "uadmin", roles: ["userAdmin"]},
|
|
},
|
|
};
|
|
|
|
// Constants that lists the privileges of a given role.
|
|
let READ_PERM = {query: 1, index_r: 1, killCursor: 1};
|
|
let READ_WRITE_PERM = {insert: 1, update: 1, remove: 1, query: 1, index_r: 1, index_w: 1, killCursor: 1};
|
|
let ADMIN_PERM = {index_r: 1, index_w: 1, profile_r: 1};
|
|
let UADMIN_PERM = {user_r: 1, user_w: 1};
|
|
let CLUSTER_PERM = {killOp: 1, currentOp: 1, fsync_unlock: 1, killCursor: 1, killAnyCursor: 1, profile_r: 1};
|
|
|
|
/**
|
|
* Checks whether an error occurs after running an operation.
|
|
*
|
|
* @param shouldPass {Boolean} true means that the operation should succeed.
|
|
* @param opFunc {function()} a function object which contains the operation to perform.
|
|
*/
|
|
let checkErr = function (shouldPass, opFunc) {
|
|
let success = true;
|
|
|
|
let exception = null;
|
|
try {
|
|
opFunc();
|
|
} catch (x) {
|
|
exception = x;
|
|
success = false;
|
|
}
|
|
|
|
assert(
|
|
success == shouldPass,
|
|
"expected shouldPass: " +
|
|
shouldPass +
|
|
", got: " +
|
|
success +
|
|
", op: " +
|
|
tojson(opFunc) +
|
|
", exception: " +
|
|
tojson(exception),
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Runs a series of operations against the db provided.
|
|
*
|
|
* @param db {DB} the database object to use.
|
|
* @param allowedActions {Object} the lists of operations that are allowed for the
|
|
* current user. The data structure is represented as a map with the presence of
|
|
* a field name means that the operation is allowed and not allowed if it is
|
|
* not present. The list of field names are: insert, update, remove, query, killOp,
|
|
* currentOp, index_r, index_w, profile_r, profile_w, user_r, user_w, killCursor,
|
|
* fsync_unlock.
|
|
*/
|
|
let testOps = function (db, allowedActions) {
|
|
checkErr(allowedActions.hasOwnProperty("insert"), function () {
|
|
let res = db.user.insert({y: 1});
|
|
if (res.hasWriteError()) throw Error("insert failed: " + tojson(res.getRawResponse()));
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("update"), function () {
|
|
let res = db.user.update({y: 1}, {z: 3});
|
|
if (res.hasWriteError()) throw Error("update failed: " + tojson(res.getRawResponse()));
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("remove"), function () {
|
|
let res = db.user.remove({y: 1});
|
|
if (res.hasWriteError()) throw Error("remove failed: " + tojson(res.getRawResponse()));
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("query"), function () {
|
|
db.user.findOne({y: 1});
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("killOp"), function () {
|
|
let errorCodeUnauthorized = 13;
|
|
let res = db.killOp(1);
|
|
|
|
if (res.code == errorCodeUnauthorized) {
|
|
throw Error("unauthorized killOp");
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("currentOp"), function () {
|
|
let errorCodeUnauthorized = 13;
|
|
let res = db.currentOp();
|
|
|
|
if (res.code == errorCodeUnauthorized) {
|
|
throw Error("unauthorized currentOp");
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("index_r"), function () {
|
|
let errorCodeUnauthorized = 13;
|
|
let res = db.runCommand({"listIndexes": "user"});
|
|
|
|
if (res.code == errorCodeUnauthorized) {
|
|
throw Error("unauthorized listIndexes");
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("index_w"), function () {
|
|
let res = db.user.createIndex({x: 1});
|
|
if (res.code == 13) {
|
|
// Unauthorized
|
|
throw Error("unauthorized currentOp");
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("profile_r"), function () {
|
|
db.system.profile.findOne();
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("profile_w"), function () {
|
|
let res = db.system.profile.insert({x: 1});
|
|
if (res.hasWriteError()) {
|
|
throw Error("profile insert failed: " + tojson(res.getRawResponse()));
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("user_r"), function () {
|
|
let result = db.runCommand({usersInfo: 1});
|
|
if (!result.ok) {
|
|
throw new Error(tojson(result));
|
|
}
|
|
});
|
|
|
|
checkErr(allowedActions.hasOwnProperty("user_w"), function () {
|
|
db.createUser({user: "a", pwd: "a", roles: jsTest.basicUserRoles});
|
|
assert(db.dropUser("a"));
|
|
});
|
|
|
|
// Test for kill cursor
|
|
const checkKillCursor = function (inTransaction) {
|
|
let newConn = new Mongo(db.getMongo().host);
|
|
let dbName = db.getName();
|
|
let db2 = newConn.getDB(dbName);
|
|
|
|
if (db2.getName() == "admin") {
|
|
assert.eq(1, db2.auth("aro", AUTH_INFO.admin.aro.pwd));
|
|
} else {
|
|
assert.eq(1, db2.auth("ro", AUTH_INFO.test.ro.pwd));
|
|
}
|
|
|
|
// Create cursor from db2.
|
|
let cmdRes = db2.runCommand({
|
|
find: db2.kill_cursor.getName(),
|
|
batchSize: 2,
|
|
...(inTransaction ? {startTransaction: true, autocommit: false, txnNumber: NumberLong(0)} : {}),
|
|
});
|
|
assert.commandWorked(cmdRes);
|
|
let cursorId = cmdRes.cursor.id;
|
|
assert(
|
|
!bsonBinaryEqual({cursorId: cursorId}, {cursorId: NumberLong(0)}),
|
|
"find command didn't return a cursor: " + tojson(cmdRes),
|
|
);
|
|
|
|
const shouldSucceed = (function () {
|
|
// admin users can do anything they want.
|
|
if (allowedActions.hasOwnProperty("killAnyCursor")) {
|
|
return true;
|
|
}
|
|
|
|
// users can kill their own cursors
|
|
const users = assert.commandWorked(db.runCommand({connectionStatus: 1})).authInfo.authenticatedUsers;
|
|
const users2 = assert.commandWorked(db2.runCommand({connectionStatus: 1})).authInfo.authenticatedUsers;
|
|
if (!users.length && !users2.length) {
|
|
// Special case, no-auth
|
|
return true;
|
|
}
|
|
return users.some(function (u) {
|
|
return users2.some(function (u2) {
|
|
return u.db === u2.db && u.user === u2.user;
|
|
});
|
|
});
|
|
})();
|
|
|
|
checkErr(shouldSucceed, function () {
|
|
// Issue killCursor command from db.
|
|
cmdRes = db.runCommand({killCursors: db2.kill_cursor.getName(), cursors: [cursorId]});
|
|
assert.commandWorked(cmdRes);
|
|
assert(
|
|
bsonBinaryEqual({cursorId: cmdRes.cursorsKilled}, {cursorId: [cursorId]}),
|
|
"unauthorized to kill cursor: " + tojson(cmdRes),
|
|
);
|
|
});
|
|
if (inTransaction) {
|
|
assert.commandWorked(db2.adminCommand({abortTransaction: 1, txnNumber: NumberLong(0), autocommit: false}));
|
|
}
|
|
};
|
|
checkKillCursor(false);
|
|
|
|
let isMongos = db.runCommand({isdbgrid: 1}).isdbgrid;
|
|
// Note: fsyncUnlock is not supported in mongos.
|
|
if (!isMongos) {
|
|
checkErr(allowedActions.hasOwnProperty("fsync_unlock"), function () {
|
|
let res = db.fsyncUnlock();
|
|
let errorCodeUnauthorized = 13;
|
|
|
|
if (res.code == errorCodeUnauthorized) {
|
|
throw Error("unauthorized fsyncUnlock");
|
|
}
|
|
});
|
|
} else {
|
|
// Requires transactions and the non-sharded version is on standalone
|
|
checkKillCursor(true);
|
|
}
|
|
};
|
|
|
|
// List of tests to run. Has the format:
|
|
//
|
|
// {
|
|
// name: {String} description of the test
|
|
// test: {function(Mongo)} the test function to run which accepts a Mongo connection
|
|
// object.
|
|
// }
|
|
let TESTS = [
|
|
{
|
|
name: "Test multiple user login separate connection",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("ro", AUTH_INFO.test.ro.pwd));
|
|
|
|
let conn2 = new Mongo(conn.host);
|
|
let testDB2 = conn2.getDB("test");
|
|
assert.eq(1, testDB2.auth("uadmin", AUTH_INFO.test.uadmin.pwd));
|
|
|
|
testOps(testDB, READ_PERM);
|
|
testOps(testDB2, UADMIN_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test user with no role",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("none", AUTH_INFO.test.none.pwd));
|
|
|
|
testOps(testDB, {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test read only user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("ro", AUTH_INFO.test.ro.pwd));
|
|
|
|
testOps(testDB, READ_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test read/write user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("rw", AUTH_INFO.test.rw.pwd));
|
|
|
|
testOps(testDB, READ_WRITE_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test read + dbAdmin user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("roadmin", AUTH_INFO.test.roadmin.pwd));
|
|
|
|
let combinedPerm = Object.extend({}, READ_PERM);
|
|
combinedPerm = Object.extend(combinedPerm, ADMIN_PERM);
|
|
testOps(testDB, combinedPerm);
|
|
},
|
|
},
|
|
{
|
|
name: "Test dbAdmin user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("admin", AUTH_INFO.test.admin.pwd));
|
|
|
|
testOps(testDB, ADMIN_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test userAdmin user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("uadmin", AUTH_INFO.test.uadmin.pwd));
|
|
|
|
testOps(testDB, UADMIN_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test cluster user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("cluster", AUTH_INFO.admin.cluster.pwd));
|
|
|
|
testOps(conn.getDB("test"), CLUSTER_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test admin user with no role",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("anone", AUTH_INFO.admin.anone.pwd));
|
|
|
|
testOps(adminDB, {});
|
|
testOps(conn.getDB("test"), {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test read only admin user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("aro", AUTH_INFO.admin.aro.pwd));
|
|
|
|
testOps(adminDB, READ_PERM);
|
|
testOps(conn.getDB("test"), {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test read/write admin user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("arw", AUTH_INFO.admin.arw.pwd));
|
|
|
|
testOps(adminDB, READ_WRITE_PERM);
|
|
testOps(conn.getDB("test"), {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test dbAdmin admin user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("aadmin", AUTH_INFO.admin.aadmin.pwd));
|
|
|
|
testOps(adminDB, ADMIN_PERM);
|
|
testOps(conn.getDB("test"), {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test userAdmin admin user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("auadmin", AUTH_INFO.admin.auadmin.pwd));
|
|
|
|
testOps(adminDB, UADMIN_PERM);
|
|
testOps(conn.getDB("test"), {});
|
|
},
|
|
},
|
|
{
|
|
name: "Test read only any db user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("any_ro", AUTH_INFO.admin.any_ro.pwd));
|
|
|
|
testOps(adminDB, READ_PERM);
|
|
testOps(conn.getDB("test"), READ_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test read/write any db user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("any_rw", AUTH_INFO.admin.any_rw.pwd));
|
|
|
|
testOps(adminDB, READ_WRITE_PERM);
|
|
testOps(conn.getDB("test"), READ_WRITE_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test dbAdmin any db user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("any_admin", AUTH_INFO.admin.any_admin.pwd));
|
|
|
|
testOps(adminDB, ADMIN_PERM);
|
|
testOps(conn.getDB("test"), ADMIN_PERM);
|
|
},
|
|
},
|
|
{
|
|
name: "Test userAdmin any db user",
|
|
test: function (conn) {
|
|
let adminDB = conn.getDB("admin");
|
|
assert.eq(1, adminDB.auth("any_uadmin", AUTH_INFO.admin.any_uadmin.pwd));
|
|
|
|
testOps(adminDB, UADMIN_PERM);
|
|
testOps(conn.getDB("test"), UADMIN_PERM);
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Test change role",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("rw", AUTH_INFO.test.rw.pwd));
|
|
|
|
let newConn = new Mongo(conn.host);
|
|
assert.eq(1, newConn.getDB("admin").auth("any_uadmin", AUTH_INFO.admin.any_uadmin.pwd));
|
|
newConn.getDB("test").updateUser("rw", {roles: ["read"]});
|
|
let origSpec = newConn.getDB("test").getUser("rw");
|
|
|
|
// role change should affect users already authenticated.
|
|
testOps(testDB, READ_PERM);
|
|
|
|
// role change should affect active connections.
|
|
testDB.runCommand({logout: 1});
|
|
assert.eq(1, testDB.auth("rw", AUTH_INFO.test.rw.pwd));
|
|
testOps(testDB, READ_PERM);
|
|
|
|
// role change should also affect new connections.
|
|
let newConn3 = new Mongo(conn.host);
|
|
let testDB3 = newConn3.getDB("test");
|
|
assert.eq(1, testDB3.auth("rw", AUTH_INFO.test.rw.pwd));
|
|
testOps(testDB3, READ_PERM);
|
|
|
|
newConn.getDB("test").updateUser("rw", {roles: origSpec.roles});
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Test override user",
|
|
test: function (conn) {
|
|
let testDB = conn.getDB("test");
|
|
assert.eq(1, testDB.auth("rw", AUTH_INFO.test.rw.pwd));
|
|
testDB.logout();
|
|
assert.eq(1, testDB.auth("ro", AUTH_INFO.test.ro.pwd));
|
|
testOps(testDB, READ_PERM);
|
|
|
|
testDB.runCommand({logout: 1});
|
|
testOps(testDB, {});
|
|
},
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Driver method for setting up the test environment, running them, cleanup
|
|
* after every test and keeping track of test failures.
|
|
*
|
|
* @param conn {Mongo} a connection to a mongod or mongos to test.
|
|
*/
|
|
let runTests = function (conn) {
|
|
let setup = function () {
|
|
let testDB = conn.getDB("test");
|
|
let adminDB = conn.getDB("admin");
|
|
|
|
adminDB.createUser({user: "root", pwd: AUTH_INFO.admin.root.pwd, roles: AUTH_INFO.admin.root.roles});
|
|
adminDB.auth("root", AUTH_INFO.admin.root.pwd);
|
|
|
|
for (let x = 0; x < 10; x++) {
|
|
testDB.kill_cursor.insert({x: x});
|
|
adminDB.kill_cursor.insert({x: x});
|
|
}
|
|
|
|
for (let dbName in AUTH_INFO) {
|
|
let dbObj = AUTH_INFO[dbName];
|
|
|
|
for (let userName in dbObj) {
|
|
if (dbName == "admin" && userName == "root") {
|
|
// We already registered this user.
|
|
continue;
|
|
}
|
|
|
|
let info = dbObj[userName];
|
|
conn.getDB(dbName).createUser({user: userName, pwd: info.pwd, roles: info.roles});
|
|
}
|
|
}
|
|
|
|
adminDB.runCommand({logout: 1});
|
|
};
|
|
|
|
let teardown = function () {
|
|
let adminDB = conn.getDB("admin");
|
|
adminDB.auth("root", AUTH_INFO.admin.root.pwd);
|
|
conn.getDB("test").dropDatabase();
|
|
adminDB.dropDatabase();
|
|
};
|
|
|
|
let failures = [];
|
|
setup();
|
|
TESTS.forEach(function (testFunc) {
|
|
try {
|
|
jsTest.log(testFunc.name);
|
|
|
|
let newConn = new Mongo(conn.host);
|
|
newConn.host = conn.host;
|
|
testFunc.test(newConn);
|
|
} catch (x) {
|
|
failures.push(testFunc.name);
|
|
jsTestLog(tojson(x));
|
|
}
|
|
});
|
|
|
|
teardown();
|
|
|
|
if (failures.length > 0) {
|
|
let list = "";
|
|
failures.forEach(function (test) {
|
|
list += test + "\n";
|
|
});
|
|
throw Error("Tests failed:\n" + list);
|
|
}
|
|
};
|
|
|
|
let conn = MongoRunner.runMongod({auth: ""});
|
|
runTests(conn);
|
|
MongoRunner.stopMongod(conn);
|
|
|
|
jsTest.log("Test sharding");
|
|
let st = new ShardingTest({shards: 1, keyFile: "jstests/libs/key1"});
|
|
runTests(st.s);
|
|
st.stop();
|
|
|
|
print("SUCCESS! Completed basic_role_auth.js");
|