/**
 * This tests that all the different commands for user manipulation all work properly for all valid
 * forms of input.
 */

function runTest(conn) {
    var authzErrorCode = 13;
    var hasAuthzError = function(result) {
        assert(result.hasWriteError());
        assert.eq(authzErrorCode, result.getWriteError().code);
    };

    conn.getDB('admin').createUser({user: 'admin', pwd: 'pwd', roles: ['root']});
    conn.getDB('admin').auth('admin', 'pwd');
    conn.getDB('admin').createUser({
        user: 'userAdmin',
        pwd: 'pwd',
        roles: ['userAdminAnyDatabase'],
        customData: {userAdmin: true}
    });
    conn.getDB('admin').logout();

    var userAdminConn = new Mongo(conn.host);
    userAdminConn.getDB('admin').auth('userAdmin', 'pwd');
    var testUserAdmin = userAdminConn.getDB('test');
    testUserAdmin.createRole({
        role: 'testRole',
        roles: [],
        privileges: [{resource: {db: 'test', collection: ''}, actions: ['viewRole']}],
    });
    userAdminConn.getDB('admin').createRole({
        role: 'adminRole',
        roles: [],
        privileges: [{resource: {cluster: true}, actions: ['connPoolSync']}]
    });

    var db = conn.getDB('test');

    // At this point there are 2 handles to the "test" database in use - "testUserAdmin" and "db".
    // "testUserAdmin" is on a connection which has been auth'd as a user with the
    // 'userAdminAnyDatabase' role.  This will be used for manipulating the user defined roles
    // used in the test.  "db" is a handle to the test database on a connection that has not
    // yet been authenticated as anyone.  This is the connection that will be used to log in as
    // various users and test that their access control is correct.

    (function testCreateUser() {
        jsTestLog("Testing createUser");

        testUserAdmin.createUser({
            user: "spencer",
            pwd: "pwd",
            customData: {zipCode: 10028},
            roles: ['readWrite', 'testRole', {role: 'adminRole', db: 'admin'}]
        });
        testUserAdmin.createUser({user: "andy", pwd: "pwd", roles: []});

        var user = testUserAdmin.getUser('spencer');
        assert.eq(10028, user.customData.zipCode);
        assert(db.auth('spencer', 'pwd'));
        assert.writeOK(db.foo.insert({a: 1}));
        assert.eq(1, db.foo.findOne().a);
        assert.doesNotThrow(function() {
            db.getRole('testRole');
        });
        assert.commandWorked(db.adminCommand('connPoolSync'));

        db.logout();
        assert(db.auth('andy', 'pwd'));
        hasAuthzError(db.foo.insert({a: 1}));
        assert.throws(function() {
            db.foo.findOne();
        });
        assert.throws(function() {
            db.getRole('testRole');
        });
    })();

    (function testUpdateUser() {
        jsTestLog("Testing updateUser");

        testUserAdmin.updateUser('spencer', {pwd: 'password', customData: {}});
        var user = testUserAdmin.getUser('spencer');
        assert.eq(null, user.customData.zipCode);
        assert(!db.auth('spencer', 'pwd'));
        assert(db.auth('spencer', 'password'));

        testUserAdmin.updateUser('spencer',
                                 {customData: {zipCode: 10036}, roles: ["read", "testRole"]});
        var user = testUserAdmin.getUser('spencer');
        assert.eq(10036, user.customData.zipCode);
        hasAuthzError(db.foo.insert({a: 1}));
        assert.eq(1, db.foo.findOne().a);
        assert.eq(1, db.foo.count());
        assert.doesNotThrow(function() {
            db.getRole('testRole');
        });
        assert.commandFailedWithCode(db.adminCommand('connPoolSync'), authzErrorCode);

        testUserAdmin.updateUser('spencer',
                                 {roles: ["readWrite", {role: 'adminRole', db: 'admin'}]});
        assert.writeOK(db.foo.update({}, {$inc: {a: 1}}));
        assert.eq(2, db.foo.findOne().a);
        assert.eq(1, db.foo.count());
        assert.throws(function() {
            db.getRole('testRole');
        });
        assert.commandWorked(db.adminCommand('connPoolSync'));
    })();

    (function testGrantRolesToUser() {
        jsTestLog("Testing grantRolesToUser");

        assert.commandFailedWithCode(db.runCommand({collMod: 'foo', usePowerOf2Sizes: true}),
                                     authzErrorCode);

        testUserAdmin.grantRolesToUser('spencer',
                                       [
                                         'readWrite',
                                         'dbAdmin',
                                         {role: 'readWrite', db: 'test'},
                                         {role: 'testRole', db: 'test'},
                                         'readWrite'
                                       ]);

        assert.commandWorked(db.runCommand({collMod: 'foo', usePowerOf2Sizes: true}));
        assert.writeOK(db.foo.update({}, {$inc: {a: 1}}));
        assert.eq(3, db.foo.findOne().a);
        assert.eq(1, db.foo.count());
        assert.doesNotThrow(function() {
            db.getRole('testRole');
        });
        assert.commandWorked(db.adminCommand('connPoolSync'));
    })();

    (function testRevokeRolesFromUser() {
        jsTestLog("Testing revokeRolesFromUser");

        testUserAdmin.revokeRolesFromUser(
            'spencer',
            [
              'readWrite',
              {role: 'dbAdmin', db: 'test2'},  // role user doesnt have
              "testRole"
            ]);

        assert.commandWorked(db.runCommand({collMod: 'foo', usePowerOf2Sizes: true}));
        hasAuthzError(db.foo.update({}, {$inc: {a: 1}}));
        assert.throws(function() {
            db.foo.findOne();
        });
        assert.throws(function() {
            db.getRole('testRole');
        });
        assert.commandWorked(db.adminCommand('connPoolSync'));

        testUserAdmin.revokeRolesFromUser('spencer', [{role: 'adminRole', db: 'admin'}]);

        hasAuthzError(db.foo.update({}, {$inc: {a: 1}}));
        assert.throws(function() {
            db.foo.findOne();
        });
        assert.throws(function() {
            db.getRole('testRole');
        });
        assert.commandFailedWithCode(db.adminCommand('connPoolSync'), authzErrorCode);

    })();

    (function testUsersInfo() {
        jsTestLog("Testing usersInfo");

        var res = testUserAdmin.runCommand({usersInfo: 'spencer'});
        printjson(res);
        assert.eq(1, res.users.length);
        assert.eq(10036, res.users[0].customData.zipCode);

        res = testUserAdmin.runCommand({usersInfo: {user: 'spencer', db: 'test'}});
        assert.eq(1, res.users.length);
        assert.eq(10036, res.users[0].customData.zipCode);

        res = testUserAdmin.runCommand({usersInfo: ['spencer', {user: 'userAdmin', db: 'admin'}]});
        printjson(res);
        assert.eq(2, res.users.length);
        if (res.users[0].user == "spencer") {
            assert.eq(10036, res.users[0].customData.zipCode);
            assert(res.users[1].customData.userAdmin);
        } else if (res.users[0].user == "userAdmin") {
            assert.eq(10036, res.users[1].customData.zipCode);
            assert(res.users[0].customData.userAdmin);
        } else {
            doassert(
                "Expected user names returned by usersInfo to be either 'userAdmin' or 'spencer', " +
                "but got: " + res.users[0].user);
        }

        res = testUserAdmin.runCommand({usersInfo: 1});
        assert.eq(2, res.users.length);
        if (res.users[0].user == "spencer") {
            assert.eq("andy", res.users[1].user);
            assert.eq(10036, res.users[0].customData.zipCode);
            assert(!res.users[1].customData);
        } else if (res.users[0].user == "andy") {
            assert.eq("spencer", res.users[1].user);
            assert(!res.users[0].customData);
            assert.eq(10036, res.users[1].customData.zipCode);
        } else {
            doassert(
                "Expected user names returned by usersInfo to be either 'andy' or 'spencer', " +
                "but got: " + res.users[0].user);
        }

    })();

    (function testDropUser() {
        jsTestLog("Testing dropUser");

        assert(db.auth('spencer', 'password'));
        assert(db.auth('andy', 'pwd'));

        assert.commandWorked(testUserAdmin.runCommand({dropUser: 'spencer'}));

        assert(!db.auth('spencer', 'password'));
        assert(db.auth('andy', 'pwd'));

        assert.eq(1, testUserAdmin.getUsers().length);
    })();

    (function testDropAllUsersFromDatabase() {
        jsTestLog("Testing dropAllUsersFromDatabase");

        assert.eq(1, testUserAdmin.getUsers().length);
        assert(db.auth('andy', 'pwd'));

        assert.commandWorked(testUserAdmin.runCommand({dropAllUsersFromDatabase: 1}));

        assert(!db.auth('andy', 'pwd'));
        assert.eq(0, testUserAdmin.getUsers().length);
    })();
}

jsTest.log('Test standalone');
var conn = MongoRunner.runMongod({auth: ''});
conn.getDB('admin').runCommand({setParameter: 1, newCollectionsUsePowerOf2Sizes: false});
runTest(conn);
MongoRunner.stopMongod(conn.port);

jsTest.log('Test sharding');
var st = new ShardingTest({shards: 2, config: 3, keyFile: 'jstests/libs/key1'});
runTest(st.s);
st.stop();
