/** * This file tests basic authorization for different roles in stand alone and sharded * environment. This file covers all types of operations except commands. */ // This test runs connectionStatus on a connection with no logged in users and auth on, which is // rejected when run with an implicit session id. // TODO SERVER-34820: Remove once connectionStatus can be run with no users authenticated in an // implicit session. TestData.disableImplicitSessions = true; /** * 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: , roles: [] } */ var 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. var READ_PERM = {query: 1, index_r: 1, killCursor: 1}; var READ_WRITE_PERM = {insert: 1, update: 1, remove: 1, query: 1, index_r: 1, index_w: 1, killCursor: 1}; var ADMIN_PERM = {index_r: 1, index_w: 1, profile_r: 1}; var UADMIN_PERM = {user_r: 1, user_w: 1}; var 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. */ var checkErr = function(shouldPass, opFunc) { var success = true; var 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. */ var testOps = function(db, allowedActions) { checkErr(allowedActions.hasOwnProperty('insert'), function() { var res = db.user.insert({y: 1}); if (res.hasWriteError()) throw Error("insert failed: " + tojson(res.getRawResponse())); }); checkErr(allowedActions.hasOwnProperty('update'), function() { var res = db.user.update({y: 1}, {z: 3}); if (res.hasWriteError()) throw Error("update failed: " + tojson(res.getRawResponse())); }); checkErr(allowedActions.hasOwnProperty('remove'), function() { var 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() { var errorCodeUnauthorized = 13; var res = db.killOp(1); if (res.code == errorCodeUnauthorized) { throw Error("unauthorized killOp"); } }); checkErr(allowedActions.hasOwnProperty('currentOp'), function() { var errorCodeUnauthorized = 13; var res = db.currentOp(); if (res.code == errorCodeUnauthorized) { throw Error("unauthorized currentOp"); } }); checkErr(allowedActions.hasOwnProperty('index_r'), function() { db.system.indexes.findOne(); }); checkErr(allowedActions.hasOwnProperty('index_w'), function() { var res = db.user.ensureIndex({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() { var res = db.system.profile.insert({x: 1}); if (res.hasWriteError()) { throw Error("profile insert failed: " + tojson(res.getRawResponse())); } }); checkErr(allowedActions.hasOwnProperty('user_r'), function() { var 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 (function() { var newConn = new Mongo(db.getMongo().host); var dbName = db.getName(); var 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. var cmdRes = db2.runCommand({find: db2.kill_cursor.getName(), batchSize: 2}); assert.commandWorked(cmdRes); var 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)); }); })(); var isMongos = db.runCommand({isdbgrid: 1}).isdbgrid; // Note: fsyncUnlock is not supported in mongos. if (!isMongos) { checkErr(allowedActions.hasOwnProperty('fsync_unlock'), function() { var res = db.fsyncUnlock(); var errorCodeUnauthorized = 13; if (res.code == errorCodeUnauthorized) { throw Error("unauthorized fsyncUnlock"); } }); } }; // 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. // } var TESTS = [ { name: 'Test multiple user login separate connection', test: function(conn) { var testDB = conn.getDB('test'); assert.eq(1, testDB.auth('ro', AUTH_INFO.test.ro.pwd)); var conn2 = new Mongo(conn.host); var 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) { var 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) { var 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) { var 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) { var testDB = conn.getDB('test'); assert.eq(1, testDB.auth('roadmin', AUTH_INFO.test.roadmin.pwd)); var combinedPerm = Object.extend({}, READ_PERM); combinedPerm = Object.extend(combinedPerm, ADMIN_PERM); testOps(testDB, combinedPerm); } }, { name: 'Test dbAdmin user', test: function(conn) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var 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) { var testDB = conn.getDB('test'); assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd)); var 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']}); var 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. var newConn3 = new Mongo(conn.host); var 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) { var testDB = conn.getDB('test'); assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd)); 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. */ var runTests = function(conn) { var setup = function() { var testDB = conn.getDB('test'); var 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 (var x = 0; x < 10; x++) { testDB.kill_cursor.insert({x: x}); adminDB.kill_cursor.insert({x: x}); } for (var dbName in AUTH_INFO) { var dbObj = AUTH_INFO[dbName]; for (var userName in dbObj) { if (dbName == 'admin' && userName == 'root') { // We already registered this user. continue; } var info = dbObj[userName]; conn.getDB(dbName).createUser({user: userName, pwd: info.pwd, roles: info.roles}); } } adminDB.runCommand({logout: 1}); }; var teardown = function() { var adminDB = conn.getDB('admin'); adminDB.auth('root', AUTH_INFO.admin.root.pwd); conn.getDB('test').dropDatabase(); adminDB.dropDatabase(); }; var failures = []; setup(); TESTS.forEach(function(testFunc) { try { jsTest.log(testFunc.name); var 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) { var list = ''; failures.forEach(function(test) { list += (test + '\n'); }); throw Error('Tests failed:\n' + list); } }; var conn = MongoRunner.runMongod({auth: ''}); runTests(conn); MongoRunner.stopMongod(conn); jsTest.log('Test sharding'); var st = new ShardingTest({shards: 1, keyFile: 'jstests/libs/key1'}); runTests(st.s); st.stop(); print('SUCCESS! Completed basic_role_auth.js');