/**
 * This test checks that cluster IP allowlists can be set and respected.
 */

print("When allowlist is empty, the server does not start.");
assert.throws(() => MongoRunner.runMongod(
                  {auth: null, keyFile: "jstests/libs/key1", clusterIpSourceAllowlist: ""}),
              [],
              "The server unexpectedly started");
// Check that the same behavior is seen with the deprecated 'clusterIpSourceWhiteList' flag.
assert.throws(() => MongoRunner.runMongod(
                  {auth: null, keyFile: "jstests/libs/key1", clusterIpSourceWhitelist: ""}),
              [],
              "The server unexpectedly started");

function emitWarningAuthErrorIsExpected(authResult) {
    if (!authResult) {
        print("***** NOTE: an authentication error is expected");
    }
}

function testIpAllowlistStartup(description, allowlistString, authResult) {
    print("Startup: " + description);

    let conn = MongoRunner.runMongod(
        {auth: null, keyFile: "jstests/libs/key1", clusterIpSourceAllowlist: allowlistString});
    assert.eq(authResult, conn.getDB("local").auth("__system", "foopdedoop"));
    emitWarningAuthErrorIsExpected(authResult);
    MongoRunner.stopMongod(conn);

    // Verify that the deprecated 'clusterIpSourceWhitelist' flag still exhibits the same behavior.
    conn = MongoRunner.runMongod(
        {auth: null, keyFile: "jstests/libs/key1", clusterIpSourceWhitelist: allowlistString});
    assert.eq(authResult, conn.getDB("local").auth("__system", "foopdedoop"));
    emitWarningAuthErrorIsExpected(authResult);
    MongoRunner.stopMongod(conn);
}

function testIpAllowlistRuntime(description, allowlistString, authResult) {
    print("Runtime: " + description);

    const conn = MongoRunner.runMongod({keyFile: "jstests/libs/key1"});
    const admin = conn.getDB('admin');
    const local = conn.getDB('local');

    // Must create a user to verify that we don't fallback to localhost exception for auth.
    assert.commandWorked(admin.runCommand({createUser: 'admin', pwd: 'admin', roles: ['root']}));

    assert(local.auth("__system", "foopdedoop"));

    print("Testing whether __system can login after set to: " + allowlistString);
    assert.commandWorked(admin.runCommand(
        {setParameter: 1, "clusterIpSourceAllowlist": allowlistString.split(",")}));
    if (!authResult) {
        // At this time we have no valid authentication, and existing session should reset.
        // We should expect admin commands to fail.
        print("Verifying that there is no longer authenticated session and admin commands fail");
        assert.commandFailed(conn.adminCommand({fsync: 1}));
    }

    let authDB = local;
    assert.eq(authResult, local.auth("__system", "foopdedoop"));
    emitWarningAuthErrorIsExpected(authResult);

    if (!authResult) {
        print("Authenticating with admin user since __system is barred");
        assert(admin.auth('admin', 'admin'));
        authDB = admin;
    }

    print("Testing that __system can login after reset to null");
    assert.commandWorked(admin.runCommand({setParameter: 1, "clusterIpSourceAllowlist": null}));
    authDB.logout();

    assert.eq(true, local.auth("__system", "foopdedoop"));
    MongoRunner.stopMongod(conn);
}

function testIpAllowlist(description, allowlistString, authResult) {
    testIpAllowlistStartup(description, allowlistString, authResult);
    testIpAllowlistRuntime(description, allowlistString, authResult);
}

function testIpAllowlistRuntimeGarbage() {
    print("Testing that garbage input is handled");

    const conn = MongoRunner.runMongod({auth: null, keyFile: "jstests/libs/key1"});
    assert.eq(true, conn.getDB("local").auth("__system", "foopdedoop"));

    const BAD_INPUTS = [
        [""],
        ["abcxyz"],
        ["1.1.1.1", "abcxyz"],
        ["1.1.1.1/abcxyz"],
        "1.1.1.1",
        1,
        {"something": "something else"},
        ["1.1.1.1", {"something": "something else"}],
    ];

    for (let bi in BAD_INPUTS) {
        print(bi);
        assert.commandFailed(conn.adminCommand({setParameter: 1, "clusterIpSourceAllowlist": bi}));
    }

    print("Testing that __system can login after reset to null");
    assert.commandWorked(conn.adminCommand({setParameter: 1, "clusterIpSourceAllowlist": null}));
    conn.getDB("local").logout();
    assert.eq(true, conn.getDB("local").auth("__system", "foopdedoop"));
    MongoRunner.stopMongod(conn);
}

testIpAllowlist(
    "When 127.0.0.1 is allowlisted, a client connected via localhost may auth as __system.",
    "127.0.0.1",
    true);

testIpAllowlist(
    "When 127.0.0.0 is allowlisted as a 24-bit CIDR block, a client connected via localhost may auth as __system.",
    "127.0.0.0/24",
    true);

testIpAllowlist(
    "When 127.0.0.5 is allowlisted as a 24-bit CIDR block, a client connected via localhost may auth as __system.",
    "127.0.0.5/24",
    true);

testIpAllowlist(
    "When 127.0.0.0 is allowlisted as a 8-bit CIDR block, a client connected via localhost may auth as __system.",
    "127.0.0.0/8",
    true);

testIpAllowlist(
    "When the IP block reserved for documentation and the 127.0.0.0/8 block are both allowlisted, a client connected via localhost may auth as __system.",
    "192.0.2.0/24,127.0.0.0/8",
    true);

testIpAllowlist(
    "When 127.0.0.0/8 and the IP block reserved for documentation are both allowlisted, a client connected via localhost may auth as __system.",
    "127.0.0.0/8,192.0.2.0/24",
    true);

testIpAllowlist(
    "When the IP block reserved for documentation and examples is allowlisted, a client connected via localhost may not auth as __system.",
    "192.0.2.0/24",
    false);

testIpAllowlistRuntimeGarbage();