summaryrefslogtreecommitdiff
path: root/jstests/serverless
diff options
context:
space:
mode:
authorjannaerin <golden.janna@gmail.com>2022-12-16 18:17:46 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-12-16 19:01:01 +0000
commit38dc821cbbdac94fa04e370f54140d26a272b507 (patch)
treefa83628160d543fe2afbc304f6f415efcb363d08 /jstests/serverless
parentce6fc190e3124c30b2dfb8a3acfed6a178b4ac30 (diff)
downloadmongo-38dc821cbbdac94fa04e370f54140d26a272b507.tar.gz
SERVER-71042 Remove featureFlagRequireTenantID from fully_disabled_feature_flags
Diffstat (limited to 'jstests/serverless')
-rw-r--r--jstests/serverless/list_databases_for_all_tenants.js7
-rw-r--r--jstests/serverless/native_tenant_data_isolation_basic_dollar_tenant.js886
-rw-r--r--jstests/serverless/native_tenant_data_isolation_basic_security_token.js907
-rw-r--r--jstests/serverless/native_tenant_data_isolation_curr_op.js242
-rw-r--r--jstests/serverless/native_tenant_data_isolation_initial_sync.js235
-rw-r--r--jstests/serverless/native_tenant_data_isolation_stop_restart.js157
6 files changed, 1177 insertions, 1257 deletions
diff --git a/jstests/serverless/list_databases_for_all_tenants.js b/jstests/serverless/list_databases_for_all_tenants.js
index cc2a34f7bde..f24be8b53ad 100644
--- a/jstests/serverless/list_databases_for_all_tenants.js
+++ b/jstests/serverless/list_databases_for_all_tenants.js
@@ -228,7 +228,7 @@ function runTestInvalidCommands(primary) {
ErrorCodes.Unauthorized);
}
-function runTestsWithMultiTenancySupport(featureFlagRequireTenantID) {
+function runTestsWithMultiTenancySupport() {
const rst = new ReplSetTest({
nodes: 2,
nodeOptions: {
@@ -236,7 +236,6 @@ function runTestsWithMultiTenancySupport(featureFlagRequireTenantID) {
setParameter: {
multitenancySupport: true,
featureFlagSecurityToken: true,
- featureFlagRequireTenantID: featureFlagRequireTenantID
}
}
});
@@ -267,7 +266,6 @@ function runTestNoMultiTenancySupport() {
setParameter: {
multitenancySupport: false,
featureFlagSecurityToken: true,
- featureFlagRequireTenantID: false
}
}
});
@@ -287,7 +285,6 @@ function runTestNoMultiTenancySupport() {
rst.stopSet();
}
-runTestsWithMultiTenancySupport(true);
-runTestsWithMultiTenancySupport(false);
+runTestsWithMultiTenancySupport();
runTestNoMultiTenancySupport();
}());
diff --git a/jstests/serverless/native_tenant_data_isolation_basic_dollar_tenant.js b/jstests/serverless/native_tenant_data_isolation_basic_dollar_tenant.js
index a464d9ec2e0..c1e91d759fb 100644
--- a/jstests/serverless/native_tenant_data_isolation_basic_dollar_tenant.js
+++ b/jstests/serverless/native_tenant_data_isolation_basic_dollar_tenant.js
@@ -4,479 +4,463 @@
"use strict";
load('jstests/aggregation/extras/utils.js'); // For arrayEq()
-
-function runTest(featureFlagRequireTenantId) {
- const rst = new ReplSetTest({
- nodes: 3,
- nodeOptions: {
- auth: '',
- setParameter:
- {multitenancySupport: true, featureFlagRequireTenantID: featureFlagRequireTenantId}
- }
- });
- rst.startSet({keyFile: 'jstests/libs/key1'});
- rst.initiate();
-
- const primary = rst.getPrimary();
- const adminDb = primary.getDB('admin');
-
- // Prepare a user for testing pass tenant via $tenant.
- // Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
- assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
- assert(adminDb.auth('admin', 'pwd'));
-
- const kTenant = ObjectId();
- const kOtherTenant = ObjectId();
- const kDbName = 'myDb';
- const kCollName = 'myColl';
- const testDb = primary.getDB(kDbName);
- const testColl = testDb.getCollection(kCollName);
-
- // In this jstest, the collection (defined by kCollName) and the document "{_id: 0, a: 1, b: 1}"
- // for the tenant (defined by kTenant) will be reused by all command tests. So, any test which
- // changes the collection name or document should reset it.
-
- // Test create and listCollections commands, plus $listCatalog aggregate, on collection.
- {
- const viewName = "view1";
- const targetViews = 'system.views';
-
- // Create a collection for the tenant kTenant, and then create a view on the collection.
- assert.commandWorked(
- testColl.getDB().createCollection(testColl.getName(), {'$tenant': kTenant}));
- assert.commandWorked(testDb.runCommand(
- {"create": viewName, "viewOn": kCollName, pipeline: [], '$tenant': kTenant}));
-
- const colls = assert.commandWorked(
- testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kTenant}));
- assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
- const expectedColls = [
- {"name": kCollName, "type": "collection"},
- {"name": targetViews, "type": "collection"},
- {"name": viewName, "type": "view"}
- ];
- assert(arrayEq(expectedColls, colls.cursor.firstBatch), tojson(colls.cursor.firstBatch));
-
- const prefixedDbName = kTenant + '_' + testDb.getName();
- const targetDb = featureFlagRequireTenantId ? testDb.getName() : prefixedDbName;
-
- // Get catalog without specifying target collection (collectionless).
- let result = adminDb.runCommand(
- {aggregate: 1, pipeline: [{$listCatalog: {}}], cursor: {}, '$tenant': kTenant});
- let resultArray = result.cursor.firstBatch;
-
- // Check that the resulting array of catalog entries contains our target databases and
- // namespaces.
- assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
-
- // Also check that the resulting array contains views specific to our target database.
- assert(
- resultArray.some((entry) => (entry.db === targetDb) && (entry.name === targetViews)));
- assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === viewName)));
-
- // Get catalog when specifying our target collection, which should only return one result.
- result = testDb.runCommand({
- aggregate: testColl.getName(),
- pipeline: [{$listCatalog: {}}],
- cursor: {},
- '$tenant': kTenant
- });
- resultArray = result.cursor.firstBatch;
-
- // Check that the resulting array of catalog entries contains our target database and
- // namespace.
- assert(resultArray.length == 1);
- assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
-
- // These collections should not be accessed with a different tenant.
- const collsWithDiffTenant = assert.commandWorked(
- testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kOtherTenant}));
- assert.eq(0, collsWithDiffTenant.cursor.firstBatch.length);
- }
-
- // Test listDatabases command.
- {
- // Create databases for kTenant. A new database is implicitly created when a collection is
- // created.
- const kOtherDbName = 'otherDb';
- assert.commandWorked(
- primary.getDB(kOtherDbName).createCollection(kCollName, {'$tenant': kTenant}));
-
- const dbs = assert.commandWorked(
- adminDb.runCommand({listDatabases: 1, nameOnly: true, '$tenant': kTenant}));
- assert.eq(2, dbs.databases.length);
- // The 'admin' database is not expected because we do not create a tenant user in this test.
- const expectedDbs = featureFlagRequireTenantId
- ? [kDbName, kOtherDbName]
- : [kTenant + "_" + kDbName, kTenant + "_" + kOtherDbName];
- assert(arrayEq(expectedDbs, dbs.databases.map(db => db.name)));
-
- // These databases should not be accessed with a different tenant.
- const dbsWithDiffTenant = assert.commandWorked(
- adminDb.runCommand({listDatabases: 1, nameOnly: true, '$tenant': kOtherTenant}));
- assert.eq(0, dbsWithDiffTenant.databases.length);
-
- const allDbs = assert.commandWorked(adminDb.runCommand({listDatabases: 1, nameOnly: true}));
- expectedDbs.push("admin");
- expectedDbs.push("config");
- expectedDbs.push("local");
-
- if (featureFlagRequireTenantId) {
- assert.eq(0, allDbs.databases.length);
- assert(arrayEq([], allDbs.databases.map(db => db.name)));
- } else {
- assert.eq(5, allDbs.databases.length);
- assert(arrayEq(expectedDbs, allDbs.databases.map(db => db.name)));
+load("jstests/libs/feature_flag_util.js"); // for isEnabled
+
+const rst = new ReplSetTest({
+ nodes: 3,
+ nodeOptions: {
+ auth: '',
+ setParameter: {
+ multitenancySupport: true,
}
}
+});
+rst.startSet({keyFile: 'jstests/libs/key1'});
+rst.initiate();
+
+const primary = rst.getPrimary();
+const adminDb = primary.getDB('admin');
+
+// Prepare a user for testing pass tenant via $tenant.
+// Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
+assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+assert(adminDb.auth('admin', 'pwd'));
+
+const featureFlagRequireTenantId = FeatureFlagUtil.isEnabled(adminDb, "RequireTenantID");
+
+const kTenant = ObjectId();
+const kOtherTenant = ObjectId();
+const kDbName = 'myDb';
+const kCollName = 'myColl';
+const testDb = primary.getDB(kDbName);
+const testColl = testDb.getCollection(kCollName);
+
+// In this jstest, the collection (defined by kCollName) and the document "{_id: 0, a: 1, b: 1}"
+// for the tenant (defined by kTenant) will be reused by all command tests. So, any test which
+// changes the collection name or document should reset it.
+
+// Test create and listCollections commands, plus $listCatalog aggregate, on collection.
+{
+ const viewName = "view1";
+ const targetViews = 'system.views';
+
+ // Create a collection for the tenant kTenant, and then create a view on the collection.
+ assert.commandWorked(
+ testColl.getDB().createCollection(testColl.getName(), {'$tenant': kTenant}));
+ assert.commandWorked(testDb.runCommand(
+ {"create": viewName, "viewOn": kCollName, pipeline: [], '$tenant': kTenant}));
+
+ const colls = assert.commandWorked(
+ testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kTenant}));
+ assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
+ const expectedColls = [
+ {"name": kCollName, "type": "collection"},
+ {"name": targetViews, "type": "collection"},
+ {"name": viewName, "type": "view"}
+ ];
+ assert(arrayEq(expectedColls, colls.cursor.firstBatch), tojson(colls.cursor.firstBatch));
+
+ const prefixedDbName = kTenant + '_' + testDb.getName();
+ const targetDb = featureFlagRequireTenantId ? testDb.getName() : prefixedDbName;
+
+ // Get catalog without specifying target collection (collectionless).
+ let result = adminDb.runCommand(
+ {aggregate: 1, pipeline: [{$listCatalog: {}}], cursor: {}, '$tenant': kTenant});
+ let resultArray = result.cursor.firstBatch;
+
+ // Check that the resulting array of catalog entries contains our target databases and
+ // namespaces.
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
+
+ // Also check that the resulting array contains views specific to our target database.
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === targetViews)));
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === viewName)));
+
+ // Get catalog when specifying our target collection, which should only return one result.
+ result = testDb.runCommand({
+ aggregate: testColl.getName(),
+ pipeline: [{$listCatalog: {}}],
+ cursor: {},
+ '$tenant': kTenant
+ });
+ resultArray = result.cursor.firstBatch;
- // Test insert, agg, find, getMore, and explain commands.
- {
- const kTenantDocs = [{w: 0}, {x: 1}, {y: 2}, {z: 3}];
- const kOtherTenantDocs = [{i: 1}, {j: 2}, {k: 3}];
-
- assert.commandWorked(
- testDb.runCommand({insert: kCollName, documents: kTenantDocs, '$tenant': kTenant}));
- assert.commandWorked(testDb.runCommand(
- {insert: kCollName, documents: kOtherTenantDocs, '$tenant': kOtherTenant}));
-
- // Check that find only returns documents from the correct tenant
- const findRes = assert.commandWorked(
- testDb.runCommand({find: kCollName, projection: {_id: 0}, '$tenant': kTenant}));
- assert.eq(kTenantDocs.length,
- findRes.cursor.firstBatch.length,
- tojson(findRes.cursor.firstBatch));
- assert(arrayEq(kTenantDocs, findRes.cursor.firstBatch), tojson(findRes.cursor.firstBatch));
-
- const findRes2 = assert.commandWorked(
- testDb.runCommand({find: kCollName, projection: {_id: 0}, '$tenant': kOtherTenant}));
- assert.eq(kOtherTenantDocs.length,
- findRes2.cursor.firstBatch.length,
- tojson(findRes2.cursor.firstBatch));
- assert(arrayEq(kOtherTenantDocs, findRes2.cursor.firstBatch),
- tojson(findRes2.cursor.firstBatch));
-
- // Test that getMore only works on a tenant's own cursor
- const cmdRes = assert.commandWorked(testDb.runCommand(
- {find: kCollName, projection: {_id: 0}, batchSize: 1, '$tenant': kTenant}));
- assert.eq(cmdRes.cursor.firstBatch.length, 1, tojson(cmdRes.cursor.firstBatch));
- assert.commandWorked(testDb.runCommand(
- {getMore: cmdRes.cursor.id, collection: kCollName, '$tenant': kTenant}));
-
- const cmdRes2 = assert.commandWorked(testDb.runCommand(
- {find: kCollName, projection: {_id: 0}, batchSize: 1, '$tenant': kTenant}));
- assert.commandFailedWithCode(
- testDb.runCommand(
- {getMore: cmdRes2.cursor.id, collection: kCollName, '$tenant': kOtherTenant}),
- ErrorCodes.Unauthorized);
-
- // Test that aggregate only finds a tenant's own document.
- const aggRes = assert.commandWorked(testDb.runCommand({
- aggregate: kCollName,
- pipeline: [{$match: {w: 0}}, {$project: {_id: 0}}],
- cursor: {},
- '$tenant': kTenant
- }));
- assert.eq(1, aggRes.cursor.firstBatch.length, tojson(aggRes.cursor.firstBatch));
- assert.eq(kTenantDocs[0], aggRes.cursor.firstBatch[0]);
-
- const aggRes2 = assert.commandWorked(testDb.runCommand({
- aggregate: kCollName,
- pipeline: [{$match: {i: 1}}, {$project: {_id: 0}}],
- cursor: {},
- '$tenant': kOtherTenant
- }));
- assert.eq(1, aggRes2.cursor.firstBatch.length, tojson(aggRes2.cursor.firstBatch));
- assert.eq(kOtherTenantDocs[0], aggRes2.cursor.firstBatch[0]);
-
- // Test that explain works correctly.
- const kTenantExplainRes = assert.commandWorked(testDb.runCommand(
- {explain: {find: kCollName}, verbosity: 'executionStats', '$tenant': kTenant}));
- assert.eq(kTenantDocs.length,
- kTenantExplainRes.executionStats.nReturned,
- tojson(kTenantExplainRes));
- const kOtherTenantExplainRes = assert.commandWorked(testDb.runCommand(
- {explain: {find: kCollName}, verbosity: 'executionStats', '$tenant': kOtherTenant}));
- assert.eq(kOtherTenantDocs.length,
- kOtherTenantExplainRes.executionStats.nReturned,
- tojson(kOtherTenantExplainRes));
- }
-
- // Test insert and findAndModify command.
- {
- assert.commandWorked(testDb.runCommand(
- {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
+ // Check that the resulting array of catalog entries contains our target database and
+ // namespace.
+ assert(resultArray.length == 1);
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
- const fad1 = assert.commandWorked(testDb.runCommand({
- findAndModify: kCollName,
- query: {a: 1},
- update: {$inc: {a: 10}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
- const fad2 = assert.commandWorked(testDb.runCommand({
- findAndModify: kCollName,
- query: {a: 11},
- update: {$set: {a: 1, b: 1}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 11, b: 1}, fad2.value);
- // This document should not be accessed with a different tenant.
- const fadOtherUser = assert.commandWorked(testDb.runCommand({
- findAndModify: kCollName,
- query: {b: 1},
- update: {$inc: {b: 10}},
- '$tenant': kOtherTenant
- }));
- assert.eq(null, fadOtherUser.value);
- }
+ // These collections should not be accessed with a different tenant.
+ const collsWithDiffTenant = assert.commandWorked(
+ testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kOtherTenant}));
+ assert.eq(0, collsWithDiffTenant.cursor.firstBatch.length);
+}
- // Test count and distinct command.
- {
- assert.commandWorked(testDb.runCommand(
- {insert: kCollName, documents: [{c: 1, d: 1}, {c: 1, d: 2}], '$tenant': kTenant}));
-
- // Test count command.
- const resCount = assert.commandWorked(
- testDb.runCommand({count: kCollName, query: {c: 1}, '$tenant': kTenant}));
- assert.eq(2, resCount.n);
- const resCountOtherUser = assert.commandWorked(
- testDb.runCommand({count: kCollName, query: {c: 1}, '$tenant': kOtherTenant}));
- assert.eq(0, resCountOtherUser.n);
-
- // Test Distict command.
- const resDistinct = assert.commandWorked(
- testDb.runCommand({distinct: kCollName, key: 'd', query: {}, '$tenant': kTenant}));
- assert.eq([1, 2], resDistinct.values.sort());
- const resDistinctOtherUser = assert.commandWorked(
- testDb.runCommand({distinct: kCollName, key: 'd', query: {}, '$tenant': kOtherTenant}));
- assert.eq([], resDistinctOtherUser.values);
+// Test listDatabases command.
+{
+ // Create databases for kTenant. A new database is implicitly created when a collection is
+ // created.
+ const kOtherDbName = 'otherDb';
+ assert.commandWorked(
+ primary.getDB(kOtherDbName).createCollection(kCollName, {'$tenant': kTenant}));
+
+ const dbs = assert.commandWorked(
+ adminDb.runCommand({listDatabases: 1, nameOnly: true, '$tenant': kTenant}));
+ assert.eq(2, dbs.databases.length);
+ // The 'admin' database is not expected because we do not create a tenant user in this test.
+ const expectedDbs = featureFlagRequireTenantId
+ ? [kDbName, kOtherDbName]
+ : [kTenant + "_" + kDbName, kTenant + "_" + kOtherDbName];
+ assert(arrayEq(expectedDbs, dbs.databases.map(db => db.name)));
+
+ // These databases should not be accessed with a different tenant.
+ const dbsWithDiffTenant = assert.commandWorked(
+ adminDb.runCommand({listDatabases: 1, nameOnly: true, '$tenant': kOtherTenant}));
+ assert.eq(0, dbsWithDiffTenant.databases.length);
+
+ const allDbs = assert.commandWorked(adminDb.runCommand({listDatabases: 1, nameOnly: true}));
+ expectedDbs.push("admin");
+ expectedDbs.push("config");
+ expectedDbs.push("local");
+
+ if (featureFlagRequireTenantId) {
+ assert.eq(0, allDbs.databases.length);
+ assert(arrayEq([], allDbs.databases.map(db => db.name)));
+ } else {
+ assert.eq(5, allDbs.databases.length);
+ assert(arrayEq(expectedDbs, allDbs.databases.map(db => db.name)));
}
+}
- // Test renameCollection command.
- {
- const fromName = kDbName + "." + kCollName;
- const toName = fromName + "_renamed";
- assert.commandWorked(adminDb.runCommand(
- {renameCollection: fromName, to: toName, dropTarget: true, '$tenant': kTenant}));
-
- // Verify the the renamed collection by findAndModify existing documents.
- const fad1 = assert.commandWorked(testDb.runCommand({
- findAndModify: kCollName + "_renamed",
- query: {a: 1},
- update: {$inc: {a: 10}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
-
- // This collection should not be accessed with a different tenant.
- assert.commandFailedWithCode(adminDb.runCommand({
- renameCollection: toName,
- to: fromName,
- dropTarget: true,
- '$tenant': kOtherTenant
- }),
- ErrorCodes.NamespaceNotFound);
-
- // Reset the collection to be used below
- assert.commandWorked(adminDb.runCommand(
- {renameCollection: toName, to: fromName, dropTarget: true, '$tenant': kTenant}));
- }
+// Test insert, agg, find, getMore, and explain commands.
+{
+ const kTenantDocs = [{w: 0}, {x: 1}, {y: 2}, {z: 3}];
+ const kOtherTenantDocs = [{i: 1}, {j: 2}, {k: 3}];
+
+ assert.commandWorked(
+ testDb.runCommand({insert: kCollName, documents: kTenantDocs, '$tenant': kTenant}));
+ assert.commandWorked(testDb.runCommand(
+ {insert: kCollName, documents: kOtherTenantDocs, '$tenant': kOtherTenant}));
+
+ // Check that find only returns documents from the correct tenant
+ const findRes = assert.commandWorked(
+ testDb.runCommand({find: kCollName, projection: {_id: 0}, '$tenant': kTenant}));
+ assert.eq(
+ kTenantDocs.length, findRes.cursor.firstBatch.length, tojson(findRes.cursor.firstBatch));
+ assert(arrayEq(kTenantDocs, findRes.cursor.firstBatch), tojson(findRes.cursor.firstBatch));
+
+ const findRes2 = assert.commandWorked(
+ testDb.runCommand({find: kCollName, projection: {_id: 0}, '$tenant': kOtherTenant}));
+ assert.eq(kOtherTenantDocs.length,
+ findRes2.cursor.firstBatch.length,
+ tojson(findRes2.cursor.firstBatch));
+ assert(arrayEq(kOtherTenantDocs, findRes2.cursor.firstBatch),
+ tojson(findRes2.cursor.firstBatch));
+
+ // Test that getMore only works on a tenant's own cursor
+ const cmdRes = assert.commandWorked(testDb.runCommand(
+ {find: kCollName, projection: {_id: 0}, batchSize: 1, '$tenant': kTenant}));
+ assert.eq(cmdRes.cursor.firstBatch.length, 1, tojson(cmdRes.cursor.firstBatch));
+ assert.commandWorked(
+ testDb.runCommand({getMore: cmdRes.cursor.id, collection: kCollName, '$tenant': kTenant}));
+
+ const cmdRes2 = assert.commandWorked(testDb.runCommand(
+ {find: kCollName, projection: {_id: 0}, batchSize: 1, '$tenant': kTenant}));
+ assert.commandFailedWithCode(
+ testDb.runCommand(
+ {getMore: cmdRes2.cursor.id, collection: kCollName, '$tenant': kOtherTenant}),
+ ErrorCodes.Unauthorized);
+
+ // Test that aggregate only finds a tenant's own document.
+ const aggRes = assert.commandWorked(testDb.runCommand({
+ aggregate: kCollName,
+ pipeline: [{$match: {w: 0}}, {$project: {_id: 0}}],
+ cursor: {},
+ '$tenant': kTenant
+ }));
+ assert.eq(1, aggRes.cursor.firstBatch.length, tojson(aggRes.cursor.firstBatch));
+ assert.eq(kTenantDocs[0], aggRes.cursor.firstBatch[0]);
+
+ const aggRes2 = assert.commandWorked(testDb.runCommand({
+ aggregate: kCollName,
+ pipeline: [{$match: {i: 1}}, {$project: {_id: 0}}],
+ cursor: {},
+ '$tenant': kOtherTenant
+ }));
+ assert.eq(1, aggRes2.cursor.firstBatch.length, tojson(aggRes2.cursor.firstBatch));
+ assert.eq(kOtherTenantDocs[0], aggRes2.cursor.firstBatch[0]);
+
+ // Test that explain works correctly.
+ const kTenantExplainRes = assert.commandWorked(testDb.runCommand(
+ {explain: {find: kCollName}, verbosity: 'executionStats', '$tenant': kTenant}));
+ assert.eq(
+ kTenantDocs.length, kTenantExplainRes.executionStats.nReturned, tojson(kTenantExplainRes));
+ const kOtherTenantExplainRes = assert.commandWorked(testDb.runCommand(
+ {explain: {find: kCollName}, verbosity: 'executionStats', '$tenant': kOtherTenant}));
+ assert.eq(kOtherTenantDocs.length,
+ kOtherTenantExplainRes.executionStats.nReturned,
+ tojson(kOtherTenantExplainRes));
+}
- // Test the dropCollection and dropDatabase commands.
- {
- // Another tenant shouldn't be able to drop the collection or database.
- assert.commandWorked(testDb.runCommand({drop: kCollName, '$tenant': kOtherTenant}));
- const collsAfterDropCollectionByOtherTenant = assert.commandWorked(testDb.runCommand(
- {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
- assert.eq(1,
- collsAfterDropCollectionByOtherTenant.cursor.firstBatch.length,
- tojson(collsAfterDropCollectionByOtherTenant.cursor.firstBatch));
-
- assert.commandWorked(testDb.runCommand({dropDatabase: 1, '$tenant': kOtherTenant}));
- const collsAfterDropDbByOtherTenant = assert.commandWorked(testDb.runCommand(
- {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
- assert.eq(1,
- collsAfterDropDbByOtherTenant.cursor.firstBatch.length,
- tojson(collsAfterDropDbByOtherTenant.cursor.firstBatch));
-
- // Now, drop the collection using the original tenantId.
- assert.commandWorked(testDb.runCommand({drop: kCollName, '$tenant': kTenant}));
- const collsAfterDropCollection = assert.commandWorked(testDb.runCommand(
- {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
- assert.eq(0,
- collsAfterDropCollection.cursor.firstBatch.length,
- tojson(collsAfterDropCollection.cursor.firstBatch));
-
- // Now, drop the database using the original tenantId.
- assert.commandWorked(testDb.runCommand({dropDatabase: 1, '$tenant': kTenant}));
- const collsAfterDropDb = assert.commandWorked(testDb.runCommand(
- {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
- assert.eq(0,
- collsAfterDropDb.cursor.firstBatch.length,
- tojson(collsAfterDropDb.cursor.firstBatch));
-
- // Reset the collection so other test cases can still access this collection with kCollName
- // after this test.
- assert.commandWorked(testDb.runCommand(
- {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
- }
+// Test insert and findAndModify command.
+{
+ assert.commandWorked(testDb.runCommand(
+ {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
+
+ const fad1 = assert.commandWorked(testDb.runCommand(
+ {findAndModify: kCollName, query: {a: 1}, update: {$inc: {a: 10}}, '$tenant': kTenant}));
+ assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
+ const fad2 = assert.commandWorked(testDb.runCommand({
+ findAndModify: kCollName,
+ query: {a: 11},
+ update: {$set: {a: 1, b: 1}},
+ '$tenant': kTenant
+ }));
+ assert.eq({_id: 0, a: 11, b: 1}, fad2.value);
+ // This document should not be accessed with a different tenant.
+ const fadOtherUser = assert.commandWorked(testDb.runCommand({
+ findAndModify: kCollName,
+ query: {b: 1},
+ update: {$inc: {b: 10}},
+ '$tenant': kOtherTenant
+ }));
+ assert.eq(null, fadOtherUser.value);
+}
- // Test that transactions can be run successfully.
- {
- const lsid =
- assert.commandWorked(testDb.runCommand({startSession: 1, $tenant: kTenant})).id;
- assert.commandWorked(testDb.runCommand({
- delete: kCollName,
- deletes: [{q: {_id: 0, a: 1, b: 1}, limit: 1}],
- startTransaction: true,
- lsid: lsid,
- txnNumber: NumberLong(0),
- autocommit: false,
- '$tenant': kTenant
- }));
- assert.commandWorked(testDb.adminCommand({
- commitTransaction: 1,
- lsid: lsid,
- txnNumber: NumberLong(0),
- autocommit: false,
- $tenant: kTenant
- }));
+// Test count and distinct command.
+{
+ assert.commandWorked(testDb.runCommand(
+ {insert: kCollName, documents: [{c: 1, d: 1}, {c: 1, d: 2}], '$tenant': kTenant}));
+
+ // Test count command.
+ const resCount = assert.commandWorked(
+ testDb.runCommand({count: kCollName, query: {c: 1}, '$tenant': kTenant}));
+ assert.eq(2, resCount.n);
+ const resCountOtherUser = assert.commandWorked(
+ testDb.runCommand({count: kCollName, query: {c: 1}, '$tenant': kOtherTenant}));
+ assert.eq(0, resCountOtherUser.n);
+
+ // Test Distict command.
+ const resDistinct = assert.commandWorked(
+ testDb.runCommand({distinct: kCollName, key: 'd', query: {}, '$tenant': kTenant}));
+ assert.eq([1, 2], resDistinct.values.sort());
+ const resDistinctOtherUser = assert.commandWorked(
+ testDb.runCommand({distinct: kCollName, key: 'd', query: {}, '$tenant': kOtherTenant}));
+ assert.eq([], resDistinctOtherUser.values);
+}
- const findRes =
- assert.commandWorked(testDb.runCommand({find: kCollName, '$tenant': kTenant}));
- assert.eq(0, findRes.cursor.firstBatch.length, tojson(findRes.cursor.firstBatch));
+// Test renameCollection command.
+{
+ const fromName = kDbName + "." + kCollName;
+ const toName = fromName + "_renamed";
+ assert.commandWorked(adminDb.runCommand(
+ {renameCollection: fromName, to: toName, dropTarget: true, '$tenant': kTenant}));
+
+ // Verify the the renamed collection by findAndModify existing documents.
+ const fad1 = assert.commandWorked(testDb.runCommand({
+ findAndModify: kCollName + "_renamed",
+ query: {a: 1},
+ update: {$inc: {a: 10}},
+ '$tenant': kTenant
+ }));
+ assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
+
+ // This collection should not be accessed with a different tenant.
+ assert.commandFailedWithCode(
+ adminDb.runCommand(
+ {renameCollection: toName, to: fromName, dropTarget: true, '$tenant': kOtherTenant}),
+ ErrorCodes.NamespaceNotFound);
+
+ // Reset the collection to be used below
+ assert.commandWorked(adminDb.runCommand(
+ {renameCollection: toName, to: fromName, dropTarget: true, '$tenant': kTenant}));
+}
- // Reset the collection so other test cases can still access this collection with kCollName
- // after this test.
- assert.commandWorked(testDb.runCommand(
- {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
- }
+// Test the dropCollection and dropDatabase commands.
+{
+ // Another tenant shouldn't be able to drop the collection or database.
+ assert.commandWorked(testDb.runCommand({drop: kCollName, '$tenant': kOtherTenant}));
+ const collsAfterDropCollectionByOtherTenant = assert.commandWorked(testDb.runCommand(
+ {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
+ assert.eq(1,
+ collsAfterDropCollectionByOtherTenant.cursor.firstBatch.length,
+ tojson(collsAfterDropCollectionByOtherTenant.cursor.firstBatch));
+
+ assert.commandWorked(testDb.runCommand({dropDatabase: 1, '$tenant': kOtherTenant}));
+ const collsAfterDropDbByOtherTenant = assert.commandWorked(testDb.runCommand(
+ {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
+ assert.eq(1,
+ collsAfterDropDbByOtherTenant.cursor.firstBatch.length,
+ tojson(collsAfterDropDbByOtherTenant.cursor.firstBatch));
+
+ // Now, drop the collection using the original tenantId.
+ assert.commandWorked(testDb.runCommand({drop: kCollName, '$tenant': kTenant}));
+ const collsAfterDropCollection = assert.commandWorked(testDb.runCommand(
+ {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
+ assert.eq(0,
+ collsAfterDropCollection.cursor.firstBatch.length,
+ tojson(collsAfterDropCollection.cursor.firstBatch));
+
+ // Now, drop the database using the original tenantId.
+ assert.commandWorked(testDb.runCommand({dropDatabase: 1, '$tenant': kTenant}));
+ const collsAfterDropDb = assert.commandWorked(testDb.runCommand(
+ {listCollections: 1, nameOnly: true, filter: {name: kCollName}, '$tenant': kTenant}));
+ assert.eq(
+ 0, collsAfterDropDb.cursor.firstBatch.length, tojson(collsAfterDropDb.cursor.firstBatch));
+
+ // Reset the collection so other test cases can still access this collection with kCollName
+ // after this test.
+ assert.commandWorked(testDb.runCommand(
+ {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
+}
- // Test createIndexes, listIndexes and dropIndexes command.
- {
- var sortIndexesByName = function(indexes) {
- return indexes.sort(function(a, b) {
- return a.name > b.name;
- });
- };
-
- var getIndexesKeyAndName = function(indexes) {
- return sortIndexesByName(indexes).map(function(index) {
- return {key: index.key, name: index.name};
- });
- };
-
- let res = assert.commandWorked(testDb.runCommand({
- createIndexes: kCollName,
- indexes: [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}],
- '$tenant': kTenant
- }));
- assert.eq(3, res.numIndexesAfter);
-
- res = assert.commandWorked(testDb.runCommand({listIndexes: kCollName, '$tenant': kTenant}));
- assert.eq(3, res.cursor.firstBatch.length);
- assert(arrayEq(
- [
- {key: {"_id": 1}, name: "_id_"},
- {key: {a: 1}, name: "indexA"},
- {key: {b: 1}, name: "indexB"}
- ],
- getIndexesKeyAndName(res.cursor.firstBatch)));
-
- // These indexes should not be accessed with a different tenant.
- assert.commandFailedWithCode(
- testDb.runCommand({listIndexes: kCollName, '$tenant': kOtherTenant}),
- ErrorCodes.NamespaceNotFound);
- assert.commandFailedWithCode(
- testDb.runCommand(
- {dropIndexes: kCollName, index: ["indexA", "indexB"], '$tenant': kOtherTenant}),
- ErrorCodes.NamespaceNotFound);
-
- // Drop those new created indexes.
- res = assert.commandWorked(testDb.runCommand(
- {dropIndexes: kCollName, index: ["indexA", "indexB"], '$tenant': kTenant}));
-
- res = assert.commandWorked(testDb.runCommand({listIndexes: kCollName, '$tenant': kTenant}));
- assert.eq(1, res.cursor.firstBatch.length);
- assert(arrayEq([{key: {"_id": 1}, name: "_id_"}],
- getIndexesKeyAndName(res.cursor.firstBatch)));
- }
+// Test that transactions can be run successfully.
+{
+ const lsid = assert.commandWorked(testDb.runCommand({startSession: 1, $tenant: kTenant})).id;
+ assert.commandWorked(testDb.runCommand({
+ delete: kCollName,
+ deletes: [{q: {_id: 0, a: 1, b: 1}, limit: 1}],
+ startTransaction: true,
+ lsid: lsid,
+ txnNumber: NumberLong(0),
+ autocommit: false,
+ '$tenant': kTenant
+ }));
+ assert.commandWorked(testDb.adminCommand({
+ commitTransaction: 1,
+ lsid: lsid,
+ txnNumber: NumberLong(0),
+ autocommit: false,
+ $tenant: kTenant
+ }));
+
+ const findRes = assert.commandWorked(testDb.runCommand({find: kCollName, '$tenant': kTenant}));
+ assert.eq(0, findRes.cursor.firstBatch.length, tojson(findRes.cursor.firstBatch));
+
+ // Reset the collection so other test cases can still access this collection with kCollName
+ // after this test.
+ assert.commandWorked(testDb.runCommand(
+ {insert: kCollName, documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
+}
- // Test collMod
- {
- // Create the index used for collMod
- let res = assert.commandWorked(testDb.runCommand({
- createIndexes: kCollName,
- indexes: [{key: {c: 1}, name: "indexC", expireAfterSeconds: 50}],
- '$tenant': kTenant
- }));
- assert.eq(2, res.numIndexesAfter);
+// Test createIndexes, listIndexes and dropIndexes command.
+{
+ var sortIndexesByName = function(indexes) {
+ return indexes.sort(function(a, b) {
+ return a.name > b.name;
+ });
+ };
- // Modifying the index without the tenantId should not work.
- res = testDb.runCommand({
- "collMod": kCollName,
- "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100},
+ var getIndexesKeyAndName = function(indexes) {
+ return sortIndexesByName(indexes).map(function(index) {
+ return {key: index.key, name: index.name};
});
+ };
+
+ let res = assert.commandWorked(testDb.runCommand({
+ createIndexes: kCollName,
+ indexes: [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}],
+ '$tenant': kTenant
+ }));
+ assert.eq(3, res.numIndexesAfter);
+
+ res = assert.commandWorked(testDb.runCommand({listIndexes: kCollName, '$tenant': kTenant}));
+ assert.eq(3, res.cursor.firstBatch.length);
+ assert(arrayEq(
+ [
+ {key: {"_id": 1}, name: "_id_"},
+ {key: {a: 1}, name: "indexA"},
+ {key: {b: 1}, name: "indexB"}
+ ],
+ getIndexesKeyAndName(res.cursor.firstBatch)));
+
+ // These indexes should not be accessed with a different tenant.
+ assert.commandFailedWithCode(
+ testDb.runCommand({listIndexes: kCollName, '$tenant': kOtherTenant}),
+ ErrorCodes.NamespaceNotFound);
+ assert.commandFailedWithCode(
+ testDb.runCommand(
+ {dropIndexes: kCollName, index: ["indexA", "indexB"], '$tenant': kOtherTenant}),
+ ErrorCodes.NamespaceNotFound);
+
+ // Drop those new created indexes.
+ res = assert.commandWorked(testDb.runCommand(
+ {dropIndexes: kCollName, index: ["indexA", "indexB"], '$tenant': kTenant}));
+
+ res = assert.commandWorked(testDb.runCommand({listIndexes: kCollName, '$tenant': kTenant}));
+ assert.eq(1, res.cursor.firstBatch.length);
+ assert(arrayEq([{key: {"_id": 1}, name: "_id_"}], getIndexesKeyAndName(res.cursor.firstBatch)));
+}
+
+// Test collMod
+{
+ // Create the index used for collMod
+ let res = assert.commandWorked(testDb.runCommand({
+ createIndexes: kCollName,
+ indexes: [{key: {c: 1}, name: "indexC", expireAfterSeconds: 50}],
+ '$tenant': kTenant
+ }));
+ assert.eq(2, res.numIndexesAfter);
+
+ // Modifying the index without the tenantId should not work.
+ res = testDb.runCommand({
+ "collMod": kCollName,
+ "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100},
+ });
+ assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
+ // TODO SERVER-70876 Uncomment out this conditional below, and remove the assertion above in
+ // favor of the assertion in the conditional.
+ /* if (featureFlagRequireTenantId) {
+ assert.commandFailedWithCode(res, 7005300);
+ } else {
assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
- // TODO SERVER-70876 Uncomment out this conditional below, and remove the assertion above in
- // favor of the assertion in the conditional.
- /* if (featureFlagRequireTenantId) {
- assert.commandFailedWithCode(res, 7005300);
- } else {
- assert.commandFailedWithCode(res, ErrorCodes.NamespaceNotFound);
- }
- */
+ }
+ */
+
+ // Modify the index with the tenantId
+ res = assert.commandWorked(testDb.runCommand({
+ "collMod": kCollName,
+ "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100},
+ '$tenant': kTenant
+ }));
+ assert.eq(50, res.expireAfterSeconds_old);
+ assert.eq(100, res.expireAfterSeconds_new);
+
+ // Drop the index created
+ assert.commandWorked(
+ testDb.runCommand({dropIndexes: kCollName, index: ["indexC"], '$tenant': kTenant}));
+}
- // Modify the index with the tenantId
- res = assert.commandWorked(testDb.runCommand({
- "collMod": kCollName,
- "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100},
+// Test the applyOps command
+{
+ if (featureFlagRequireTenantId) {
+ assert.commandWorked(testDb.runCommand({
+ applyOps:
+ [{"op": "i", "ns": testColl.getFullName(), "tid": kTenant, "o": {_id: 5, x: 17}}],
'$tenant': kTenant
}));
- assert.eq(50, res.expireAfterSeconds_old);
- assert.eq(100, res.expireAfterSeconds_new);
-
- // Drop the index created
- assert.commandWorked(
- testDb.runCommand({dropIndexes: kCollName, index: ["indexC"], '$tenant': kTenant}));
- }
-
- // Test the applyOps command
- {
- if (featureFlagRequireTenantId) {
- assert.commandWorked(testDb.runCommand({
- applyOps: [
- {"op": "i", "ns": testColl.getFullName(), "tid": kTenant, "o": {_id: 5, x: 17}}
- ],
- '$tenant': kTenant
- }));
- } else {
- const ns = kTenant + '_' + testColl.getFullName();
- assert.commandWorked(testDb.runCommand(
- {applyOps: [{"op": "i", "ns": ns, "o": {_id: 5, x: 17}}], '$tenant': kTenant}));
- }
-
- // Check applyOp inserted the document.
- const findRes = assert.commandWorked(
- testDb.runCommand({find: kCollName, filter: {_id: 5}, '$tenant': kTenant}));
- assert.eq(1, findRes.cursor.firstBatch.length);
- assert.eq(17, findRes.cursor.firstBatch[0].x);
- }
-
- // Test the validate command.
- {
- const validateRes =
- assert.commandWorked(testDb.runCommand({validate: kCollName, '$tenant': kTenant}));
- assert(validateRes.valid);
+ } else {
+ const ns = kTenant + '_' + testColl.getFullName();
+ assert.commandWorked(testDb.runCommand(
+ {applyOps: [{"op": "i", "ns": ns, "o": {_id: 5, x: 17}}], '$tenant': kTenant}));
}
- // Test dbCheck command.
- { assert.commandWorked(testDb.runCommand({dbCheck: kCollName, '$tenant': kTenant})); }
+ // Check applyOp inserted the document.
+ const findRes = assert.commandWorked(
+ testDb.runCommand({find: kCollName, filter: {_id: 5}, '$tenant': kTenant}));
+ assert.eq(1, findRes.cursor.firstBatch.length);
+ assert.eq(17, findRes.cursor.firstBatch[0].x);
+}
- rst.stopSet();
+// Test the validate command.
+{
+ const validateRes =
+ assert.commandWorked(testDb.runCommand({validate: kCollName, '$tenant': kTenant}));
+ assert(validateRes.valid);
}
-runTest(true);
-runTest(false);
+// Test dbCheck command.
+{ assert.commandWorked(testDb.runCommand({dbCheck: kCollName, '$tenant': kTenant})); }
+
+rst.stopSet();
})();
diff --git a/jstests/serverless/native_tenant_data_isolation_basic_security_token.js b/jstests/serverless/native_tenant_data_isolation_basic_security_token.js
index 52659cf126a..62502f7ca39 100644
--- a/jstests/serverless/native_tenant_data_isolation_basic_security_token.js
+++ b/jstests/serverless/native_tenant_data_isolation_basic_security_token.js
@@ -4,6 +4,7 @@
"use strict";
load('jstests/aggregation/extras/utils.js'); // For arrayEq()
+load("jstests/libs/feature_flag_util.js"); // for isEnabled
function checkNsSerializedCorrectly(
featureFlagRequireTenantId, kTenant, kDbName, kCollectionName, nsField) {
@@ -20,510 +21,486 @@ function checkNsSerializedCorrectly(
}
}
-function runTest(featureFlagRequireTenantId) {
- const rst = new ReplSetTest({
- nodes: 3,
- nodeOptions: {
- auth: '',
- setParameter: {
- multitenancySupport: true,
- featureFlagSecurityToken: true,
- featureFlagRequireTenantID: featureFlagRequireTenantId
- }
+const rst = new ReplSetTest({
+ nodes: 3,
+ nodeOptions: {
+ auth: '',
+ setParameter: {
+ multitenancySupport: true,
+ featureFlagSecurityToken: true,
}
- });
- rst.startSet({keyFile: 'jstests/libs/key1'});
- rst.initiate();
-
- const primary = rst.getPrimary();
- const adminDb = primary.getDB('admin');
-
- // Prepare a user for testing pass tenant via $tenant.
- // Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
- assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
- assert(adminDb.auth('admin', 'pwd'));
-
- const kTenant = ObjectId();
- const kOtherTenant = ObjectId();
- const kDbName = 'myDb';
- const kCollName = 'myColl';
- const tokenConn = new Mongo(primary.host);
- const securityToken =
- _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant});
- const tokenDB = tokenConn.getDB(kDbName);
-
- // In this jstest, the collection (defined by kCollName) and the document "{_id: 0, a: 1, b: 1}"
- // for the tenant (defined by kTenant) will be reused by all command tests. So, any test which
- // changes the collection name or document should reset it.
-
- // Test commands using a security token for one tenant.
+ }
+});
+rst.startSet({keyFile: 'jstests/libs/key1'});
+rst.initiate();
+
+const primary = rst.getPrimary();
+const adminDb = primary.getDB('admin');
+
+// Prepare a user for testing pass tenant via $tenant.
+// Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
+assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+assert(adminDb.auth('admin', 'pwd'));
+
+const featureFlagRequireTenantId = FeatureFlagUtil.isEnabled(
+ adminDb,
+ "RequireTenantID",
+);
+
+const kTenant = ObjectId();
+const kOtherTenant = ObjectId();
+const kDbName = 'myDb';
+const kCollName = 'myColl';
+const tokenConn = new Mongo(primary.host);
+const securityToken = _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant});
+const tokenDB = tokenConn.getDB(kDbName);
+
+// In this jstest, the collection (defined by kCollName) and the document "{_id: 0, a: 1, b: 1}"
+// for the tenant (defined by kTenant) will be reused by all command tests. So, any test which
+// changes the collection name or document should reset it.
+
+// Test commands using a security token for one tenant.
+{
+ // Create a user for kTenant and then set the security token on the connection.
+ assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant1",
+ '$tenant': kTenant,
+ roles:
+ [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+ }));
+
+ tokenConn._setSecurityToken(securityToken);
+
+ // Logout the root user to avoid multiple authentication.
+ tokenConn.getDB("admin").logout();
+
+ // Create a collection for the tenant kTenant and then insert into it.
+ assert.commandWorked(tokenDB.createCollection(kCollName));
+ assert.commandWorked(
+ tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
+
+ // Find the document and call getMore on the cursor
{
- // Create a user for kTenant and then set the security token on the connection.
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant1",
- '$tenant': kTenant,
- roles: [
- {role: 'dbAdminAnyDatabase', db: 'admin'},
- {role: 'readWriteAnyDatabase', db: 'admin'}
- ]
- }));
-
- tokenConn._setSecurityToken(securityToken);
-
- // Logout the root user to avoid multiple authentication.
- tokenConn.getDB("admin").logout();
-
- // Create a collection for the tenant kTenant and then insert into it.
- assert.commandWorked(tokenDB.createCollection(kCollName));
+ // Add additional document in order to have two batches for getMore
assert.commandWorked(
- tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
+ tokenDB.runCommand({insert: kCollName, documents: [{_id: 100, x: 1}]}));
+
+ const findRes = assert.commandWorked(
+ tokenDB.runCommand({find: kCollName, filter: {a: 1}, batchSize: 1}));
+ assert(arrayEq([{_id: 0, a: 1, b: 1}], findRes.cursor.firstBatch), tojson(findRes));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, findRes.cursor.ns);
+
+ const getMoreRes = assert.commandWorked(
+ tokenDB.runCommand({getMore: findRes.cursor.id, collection: kCollName}));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, getMoreRes.cursor.ns);
+ }
- // Find the document and call getMore on the cursor
- {
- // Add additional document in order to have two batches for getMore
- assert.commandWorked(
- tokenDB.runCommand({insert: kCollName, documents: [{_id: 100, x: 1}]}));
-
- const findRes = assert.commandWorked(
- tokenDB.runCommand({find: kCollName, filter: {a: 1}, batchSize: 1}));
- assert(arrayEq([{_id: 0, a: 1, b: 1}], findRes.cursor.firstBatch), tojson(findRes));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, findRes.cursor.ns);
-
- const getMoreRes = assert.commandWorked(
- tokenDB.runCommand({getMore: findRes.cursor.id, collection: kCollName}));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, getMoreRes.cursor.ns);
- }
+ // Test the aggregate command.
+ {
+ const aggRes = assert.commandWorked(
+ tokenDB.runCommand({aggregate: kCollName, pipeline: [{$match: {a: 1}}], cursor: {}}));
+ assert(arrayEq([{_id: 0, a: 1, b: 1}], aggRes.cursor.firstBatch), tojson(aggRes));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, aggRes.cursor.ns);
+ }
- // Test the aggregate command.
- {
- const aggRes = assert.commandWorked(tokenDB.runCommand(
- {aggregate: kCollName, pipeline: [{$match: {a: 1}}], cursor: {}}));
- assert(arrayEq([{_id: 0, a: 1, b: 1}], aggRes.cursor.firstBatch), tojson(aggRes));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, aggRes.cursor.ns);
- }
+ // Find and modify the document.
+ {
+ const fad1 = assert.commandWorked(
+ tokenDB.runCommand({findAndModify: kCollName, query: {a: 1}, update: {$inc: {a: 10}}}));
+ assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
+ const fad2 = assert.commandWorked(tokenDB.runCommand(
+ {findAndModify: kCollName, query: {a: 11}, update: {$set: {a: 1, b: 1}}}));
+ assert.eq({_id: 0, a: 11, b: 1}, fad2.value);
+ }
- // Find and modify the document.
- {
- const fad1 = assert.commandWorked(tokenDB.runCommand(
- {findAndModify: kCollName, query: {a: 1}, update: {$inc: {a: 10}}}));
- assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
- const fad2 = assert.commandWorked(tokenDB.runCommand(
- {findAndModify: kCollName, query: {a: 11}, update: {$set: {a: 1, b: 1}}}));
- assert.eq({_id: 0, a: 11, b: 1}, fad2.value);
- }
+ // Create a view on the collection, and check that listCollections sees the original
+ // collection, the view, and the system.views collection. Then, call $listCatalog on
+ // the collection and views and validate the results.
+ {
+ const viewName = "view1";
+ const targetViews = 'system.views';
- // Create a view on the collection, and check that listCollections sees the original
- // collection, the view, and the system.views collection. Then, call $listCatalog on
- // the collection and views and validate the results.
- {
- const viewName = "view1";
- const targetViews = 'system.views';
-
- assert.commandWorked(
- tokenDB.runCommand({"create": viewName, "viewOn": kCollName, pipeline: []}));
-
- const colls =
- assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
- assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
- const expectedColls = [
- {"name": kCollName, "type": "collection"},
- {"name": targetViews, "type": "collection"},
- {"name": viewName, "type": "view"}
- ];
- assert(arrayEq(expectedColls, colls.cursor.firstBatch),
- tojson(colls.cursor.firstBatch));
- checkNsSerializedCorrectly(featureFlagRequireTenantId,
- kTenant,
- kDbName,
- "$cmd.listCollections",
- colls.cursor.ns);
-
- const prefixedDbName = kTenant + '_' + tokenDB.getName();
- const targetDb = featureFlagRequireTenantId ? tokenDB.getName() : prefixedDbName;
-
- const tokenAdminDB = tokenConn.getDB('admin');
-
- // Get catalog without specifying target collection (collectionless).
- let result =
- tokenAdminDB.runCommand({aggregate: 1, pipeline: [{$listCatalog: {}}], cursor: {}});
- let resultArray = result.cursor.firstBatch;
-
- // Check that the resulting array of catalog entries contains our target databases and
- // namespaces.
- assert(
- resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
-
- // Also check that the resulting array contains views specific to our target database.
- assert(resultArray.some((entry) =>
- (entry.db === targetDb) && (entry.name === targetViews)));
- assert(
- resultArray.some((entry) => (entry.db === targetDb) && (entry.name === viewName)));
-
- // Get catalog when specifying our target collection, which should only return one
- // result.
- result = tokenDB.runCommand(
- {aggregate: kCollName, pipeline: [{$listCatalog: {}}], cursor: {}});
- resultArray = result.cursor.firstBatch;
-
- // Check that the resulting array of catalog entries contains our target databases and
- // namespaces.
- assert(resultArray.length == 1);
- assert(
- resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
- }
+ assert.commandWorked(
+ tokenDB.runCommand({"create": viewName, "viewOn": kCollName, pipeline: []}));
- // Test explain command with find
- {
- const cmdRes = tokenDB.runCommand({explain: {find: kCollName, filter: {a: 1}}});
- assert.eq(1, cmdRes.executionStats.nReturned, tojson(cmdRes));
- checkNsSerializedCorrectly(featureFlagRequireTenantId,
- kTenant,
- kDbName,
- kCollName,
- cmdRes.queryPlanner.namespace);
- }
+ const colls =
+ assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
+ assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
+ const expectedColls = [
+ {"name": kCollName, "type": "collection"},
+ {"name": targetViews, "type": "collection"},
+ {"name": viewName, "type": "view"}
+ ];
+ assert(arrayEq(expectedColls, colls.cursor.firstBatch), tojson(colls.cursor.firstBatch));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, "$cmd.listCollections", colls.cursor.ns);
+
+ const prefixedDbName = kTenant + '_' + tokenDB.getName();
+ const targetDb = featureFlagRequireTenantId ? tokenDB.getName() : prefixedDbName;
+
+ const tokenAdminDB = tokenConn.getDB('admin');
+
+ // Get catalog without specifying target collection (collectionless).
+ let result =
+ tokenAdminDB.runCommand({aggregate: 1, pipeline: [{$listCatalog: {}}], cursor: {}});
+ let resultArray = result.cursor.firstBatch;
+
+ // Check that the resulting array of catalog entries contains our target databases and
+ // namespaces.
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
+
+ // Also check that the resulting array contains views specific to our target database.
+ assert(
+ resultArray.some((entry) => (entry.db === targetDb) && (entry.name === targetViews)));
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === viewName)));
+
+ // Get catalog when specifying our target collection, which should only return one
+ // result.
+ result =
+ tokenDB.runCommand({aggregate: kCollName, pipeline: [{$listCatalog: {}}], cursor: {}});
+ resultArray = result.cursor.firstBatch;
+
+ // Check that the resulting array of catalog entries contains our target databases and
+ // namespaces.
+ assert(resultArray.length == 1);
+ assert(resultArray.some((entry) => (entry.db === targetDb) && (entry.name === kCollName)));
+ }
- // Test count and distinct command.
- {
- assert.commandWorked(tokenDB.runCommand(
- {insert: kCollName, documents: [{_id: 1, c: 1, d: 1}, {_id: 2, c: 1, d: 2}]}));
+ // Test explain command with find
+ {
+ const cmdRes = tokenDB.runCommand({explain: {find: kCollName, filter: {a: 1}}});
+ assert.eq(1, cmdRes.executionStats.nReturned, tojson(cmdRes));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, cmdRes.queryPlanner.namespace);
+ }
- const resCount =
- assert.commandWorked(tokenDB.runCommand({count: kCollName, query: {c: 1}}));
- assert.eq(2, resCount.n);
+ // Test count and distinct command.
+ {
+ assert.commandWorked(tokenDB.runCommand(
+ {insert: kCollName, documents: [{_id: 1, c: 1, d: 1}, {_id: 2, c: 1, d: 2}]}));
- const resDitinct = assert.commandWorked(
- tokenDB.runCommand({distinct: kCollName, key: 'd', query: {}}));
- assert.eq([1, 2], resDitinct.values.sort());
- }
+ const resCount =
+ assert.commandWorked(tokenDB.runCommand({count: kCollName, query: {c: 1}}));
+ assert.eq(2, resCount.n);
- // Rename the collection.
- {
- const fromName = kDbName + '.' + kCollName;
- const toName = fromName + "_renamed";
- const tokenAdminDB = tokenConn.getDB('admin');
- assert.commandWorked(tokenAdminDB.runCommand(
- {renameCollection: fromName, to: toName, dropTarget: true}));
-
- // Verify the the renamed collection by findAndModify existing documents.
- const fad1 = assert.commandWorked(tokenDB.runCommand({
- findAndModify: kCollName + "_renamed",
- query: {a: 1},
- update: {$set: {a: 11, b: 1}}
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
-
- // Reset the collection name and document data.
- assert.commandWorked(tokenAdminDB.runCommand(
- {renameCollection: toName, to: fromName, dropTarget: true}));
- assert.commandWorked(tokenDB.runCommand(
- {findAndModify: kCollName, query: {a: 11}, update: {$set: {a: 1, b: 1}}}));
- }
+ const resDitinct =
+ assert.commandWorked(tokenDB.runCommand({distinct: kCollName, key: 'd', query: {}}));
+ assert.eq([1, 2], resDitinct.values.sort());
+ }
- // ListDatabases only returns databases associated with kTenant.
- {
- // Create databases for kTenant. A new database is implicitly created when a collection
- // is created.
- const kOtherDbName = 'otherDb';
- assert.commandWorked(tokenConn.getDB(kOtherDbName).createCollection("collName"));
- const tokenAdminDB = tokenConn.getDB('admin');
- const dbs =
- assert.commandWorked(tokenAdminDB.runCommand({listDatabases: 1, nameOnly: true}));
- assert.eq(3, dbs.databases.length);
- const expectedDbs = featureFlagRequireTenantId
- ? ["admin", kDbName, kOtherDbName]
- : [kTenant + "_admin", kTenant + "_" + kDbName, kTenant + "_" + kOtherDbName];
- assert(arrayEq(expectedDbs, dbs.databases.map(db => db.name)));
- }
+ // Rename the collection.
+ {
+ const fromName = kDbName + '.' + kCollName;
+ const toName = fromName + "_renamed";
+ const tokenAdminDB = tokenConn.getDB('admin');
+ assert.commandWorked(
+ tokenAdminDB.runCommand({renameCollection: fromName, to: toName, dropTarget: true}));
- {
- // Test the collStats command.
- let res = assert.commandWorked(tokenDB.runCommand({collStats: kCollName}));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, res.ns);
-
- // perform the same test on a timeseries collection
- const timeFieldName = "time";
- const tsColl = "timeseriesColl";
- assert.commandWorked(
- tokenDB.createCollection(tsColl, {timeseries: {timeField: timeFieldName}}));
- res = assert.commandWorked(tokenDB.runCommand({collStats: tsColl}));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, tsColl, res.ns);
- checkNsSerializedCorrectly(featureFlagRequireTenantId,
- kTenant,
- kDbName,
- 'system.buckets.' + tsColl,
- res.timeseries.bucketsNs);
- }
+ // Verify the the renamed collection by findAndModify existing documents.
+ const fad1 = assert.commandWorked(tokenDB.runCommand(
+ {findAndModify: kCollName + "_renamed", query: {a: 1}, update: {$set: {a: 11, b: 1}}}));
+ assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
- // Drop the collection, and then the database. Check that listCollections no longer returns
- // the 3 collections.
- {
- // Drop the collection, and check that the "ns" returned is serialized correctly.
- const dropRes = assert.commandWorked(tokenDB.runCommand({drop: kCollName}));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, dropRes.ns);
-
- const collsAfterDropColl = assert.commandWorked(tokenDB.runCommand(
- {listCollections: 1, nameOnly: true, filter: {name: kCollName}}));
- assert.eq(0,
- collsAfterDropColl.cursor.firstBatch.length,
- tojson(collsAfterDropColl.cursor.firstBatch));
-
- assert.commandWorked(tokenDB.runCommand({dropDatabase: 1}));
- const collsAfterDropDb =
- assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
- assert.eq(0,
- collsAfterDropDb.cursor.firstBatch.length,
- tojson(collsAfterDropDb.cursor.firstBatch));
-
- // Reset the collection and document.
- assert.commandWorked(
- tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
- }
+ // Reset the collection name and document data.
+ assert.commandWorked(
+ tokenAdminDB.runCommand({renameCollection: toName, to: fromName, dropTarget: true}));
+ assert.commandWorked(tokenDB.runCommand(
+ {findAndModify: kCollName, query: {a: 11}, update: {$set: {a: 1, b: 1}}}));
+ }
- // Test that transactions can be run successfully.
- {
- const session = tokenDB.getMongo().startSession();
- const sessionDb = session.getDatabase(kDbName);
- session.startTransaction();
- assert.commandWorked(sessionDb.runCommand(
- {delete: kCollName, deletes: [{q: {_id: 0, a: 1, b: 1}, limit: 1}]}));
- session.commitTransaction_forTesting();
-
- const findRes = assert.commandWorked(tokenDB.runCommand({find: kCollName}));
- assert.eq(0, findRes.cursor.firstBatch.length, tojson(findRes.cursor.firstBatch));
-
- // Reset the collection and document.
- assert.commandWorked(
- tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
- }
+ // ListDatabases only returns databases associated with kTenant.
+ {
+ // Create databases for kTenant. A new database is implicitly created when a collection
+ // is created.
+ const kOtherDbName = 'otherDb';
+ assert.commandWorked(tokenConn.getDB(kOtherDbName).createCollection("collName"));
+ const tokenAdminDB = tokenConn.getDB('admin');
+ const dbs =
+ assert.commandWorked(tokenAdminDB.runCommand({listDatabases: 1, nameOnly: true}));
+ assert.eq(3, dbs.databases.length);
+ const expectedDbs = featureFlagRequireTenantId
+ ? ["admin", kDbName, kOtherDbName]
+ : [kTenant + "_admin", kTenant + "_" + kDbName, kTenant + "_" + kOtherDbName];
+ assert(arrayEq(expectedDbs, dbs.databases.map(db => db.name)));
+ }
- // Test createIndexes, listIndexes and dropIndexes command.
- {
- var sortIndexesByName = function(indexes) {
- return indexes.sort(function(a, b) {
- return a.name > b.name;
- });
- };
-
- var getIndexesKeyAndName = function(indexes) {
- return sortIndexesByName(indexes).map(function(index) {
- return {key: index.key, name: index.name};
- });
- };
-
- let res = assert.commandWorked(tokenDB.runCommand({
- createIndexes: kCollName,
- indexes: [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}]
- }));
- assert.eq(3, res.numIndexesAfter);
-
- res = assert.commandWorked(tokenDB.runCommand({listIndexes: kCollName}));
- assert.eq(3, res.cursor.firstBatch.length);
- assert(arrayEq(
- [
- {key: {"_id": 1}, name: "_id_"},
- {key: {a: 1}, name: "indexA"},
- {key: {b: 1}, name: "indexB"}
- ],
- getIndexesKeyAndName(res.cursor.firstBatch)));
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, res.cursor.ns);
-
- // Drop those new created indexes.
- res = assert.commandWorked(
- tokenDB.runCommand({dropIndexes: kCollName, index: ["indexA", "indexB"]}));
-
- res = assert.commandWorked(tokenDB.runCommand({listIndexes: kCollName}));
- assert.eq(1, res.cursor.firstBatch.length);
- assert(arrayEq([{key: {"_id": 1}, name: "_id_"}],
- getIndexesKeyAndName(res.cursor.firstBatch)));
- }
+ {
+ // Test the collStats command.
+ let res = assert.commandWorked(tokenDB.runCommand({collStats: kCollName}));
+ checkNsSerializedCorrectly(featureFlagRequireTenantId, kTenant, kDbName, kCollName, res.ns);
- // Test the validate command.
- {
- const validateRes = assert.commandWorked(tokenDB.runCommand({validate: kCollName}));
- assert(validateRes.valid);
- checkNsSerializedCorrectly(
- featureFlagRequireTenantId, kTenant, kDbName, kCollName, validateRes.ns);
- }
+ // perform the same test on a timeseries collection
+ const timeFieldName = "time";
+ const tsColl = "timeseriesColl";
+ assert.commandWorked(
+ tokenDB.createCollection(tsColl, {timeseries: {timeField: timeFieldName}}));
+ res = assert.commandWorked(tokenDB.runCommand({collStats: tsColl}));
+ checkNsSerializedCorrectly(featureFlagRequireTenantId, kTenant, kDbName, tsColl, res.ns);
+ checkNsSerializedCorrectly(featureFlagRequireTenantId,
+ kTenant,
+ kDbName,
+ 'system.buckets.' + tsColl,
+ res.timeseries.bucketsNs);
}
- // Test commands using a security token for a different tenant and check that this tenant cannot
- // access the other tenant's collection.
+ // Drop the collection, and then the database. Check that listCollections no longer returns
+ // the 3 collections.
{
- // Create a user for a different tenant, and set the security token on the connection.
- // We reuse the same connection, but swap the token out.
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant2",
- '$tenant': kOtherTenant,
- roles: [
- {role: 'dbAdminAnyDatabase', db: 'admin'},
- {role: 'readWriteAnyDatabase', db: 'admin'}
- ]
- }));
- const securityTokenOtherTenant =
- _createSecurityToken({user: "userTenant2", db: '$external', tenant: kOtherTenant});
- tokenConn._setSecurityToken(securityTokenOtherTenant);
-
- const tokenDB2 = tokenConn.getDB(kDbName);
+ // Drop the collection, and check that the "ns" returned is serialized correctly.
+ const dropRes = assert.commandWorked(tokenDB.runCommand({drop: kCollName}));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, dropRes.ns);
+
+ const collsAfterDropColl = assert.commandWorked(
+ tokenDB.runCommand({listCollections: 1, nameOnly: true, filter: {name: kCollName}}));
+ assert.eq(0,
+ collsAfterDropColl.cursor.firstBatch.length,
+ tojson(collsAfterDropColl.cursor.firstBatch));
+
+ assert.commandWorked(tokenDB.runCommand({dropDatabase: 1}));
+ const collsAfterDropDb =
+ assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
+ assert.eq(0,
+ collsAfterDropDb.cursor.firstBatch.length,
+ tojson(collsAfterDropDb.cursor.firstBatch));
- const findOtherUser =
- assert.commandWorked(tokenDB2.runCommand({find: kCollName, filter: {a: 1}}));
- assert.eq(findOtherUser.cursor.firstBatch.length, 0, tojson(findOtherUser));
+ // Reset the collection and document.
+ assert.commandWorked(
+ tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
+ }
- const explainOtherUser =
- assert.commandWorked(tokenDB2.runCommand({explain: {find: kCollName, filter: {a: 1}}}));
- assert.eq(explainOtherUser.executionStats.nReturned, 0, tojson(explainOtherUser));
+ // Test that transactions can be run successfully.
+ {
+ const session = tokenDB.getMongo().startSession();
+ const sessionDb = session.getDatabase(kDbName);
+ session.startTransaction();
+ assert.commandWorked(sessionDb.runCommand(
+ {delete: kCollName, deletes: [{q: {_id: 0, a: 1, b: 1}, limit: 1}]}));
+ session.commitTransaction_forTesting();
- const fadOtherUser = assert.commandWorked(tokenDB2.runCommand(
- {findAndModify: kCollName, query: {b: 1}, update: {$inc: {b: 10}}}));
- assert.eq(null, fadOtherUser.value);
+ const findRes = assert.commandWorked(tokenDB.runCommand({find: kCollName}));
+ assert.eq(0, findRes.cursor.firstBatch.length, tojson(findRes.cursor.firstBatch));
- const countOtherUser =
- assert.commandWorked(tokenDB2.runCommand({count: kCollName, query: {c: 1}}));
- assert.eq(0, countOtherUser.n);
+ // Reset the collection and document.
+ assert.commandWorked(
+ tokenDB.runCommand({insert: kCollName, documents: [{_id: 0, a: 1, b: 1}]}));
+ }
- const distinctOtherUer =
- assert.commandWorked(tokenDB2.runCommand({distinct: kCollName, key: 'd', query: {}}));
- assert.eq([], distinctOtherUer.values);
+ // Test createIndexes, listIndexes and dropIndexes command.
+ {
+ var sortIndexesByName = function(indexes) {
+ return indexes.sort(function(a, b) {
+ return a.name > b.name;
+ });
+ };
+
+ var getIndexesKeyAndName = function(indexes) {
+ return sortIndexesByName(indexes).map(function(index) {
+ return {key: index.key, name: index.name};
+ });
+ };
- const fromName = kDbName + '.' + kCollName;
- const toName = fromName + "_renamed";
- assert.commandFailedWithCode(
- tokenConn.getDB("admin").runCommand(
- {renameCollection: fromName, to: toName, dropTarget: true}),
- ErrorCodes.NamespaceNotFound);
-
- assert.commandFailedWithCode(tokenDB2.runCommand({listIndexes: kCollName}),
- ErrorCodes.NamespaceNotFound);
- assert.commandFailedWithCode(
- tokenDB2.runCommand({dropIndexes: kCollName, index: ["indexA", "indexB"]}),
- ErrorCodes.NamespaceNotFound);
-
- assert.commandFailedWithCode(tokenDB2.runCommand({validate: kCollName}),
- ErrorCodes.NamespaceNotFound);
-
- // ListDatabases with securityToken of kOtherTenant cannot access databases created by
- // kTenant.
- const dbsWithDiffToken = assert.commandWorked(
- tokenConn.getDB('admin').runCommand({listDatabases: 1, nameOnly: true}));
- // Only the 'admin' db exists
- assert.eq(1, dbsWithDiffToken.databases.length);
- const expectedAdminDb = featureFlagRequireTenantId ? "admin" : kOtherTenant + "_admin";
- assert(arrayEq([expectedAdminDb], dbsWithDiffToken.databases.map(db => db.name)));
-
- // Attempt to drop the database, then check it was not dropped.
- assert.commandWorked(tokenDB2.runCommand({dropDatabase: 1}));
-
- // Run listCollections using the original user's security token to see the collection
- // exists.
- tokenConn._setSecurityToken(securityToken);
- const collsAfterDropOtherTenant =
- assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
- assert.eq(1,
- collsAfterDropOtherTenant.cursor.firstBatch.length,
- tojson(collsAfterDropOtherTenant.cursor.firstBatch));
+ let res = assert.commandWorked(tokenDB.runCommand({
+ createIndexes: kCollName,
+ indexes: [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}]
+ }));
+ assert.eq(3, res.numIndexesAfter);
+
+ res = assert.commandWorked(tokenDB.runCommand({listIndexes: kCollName}));
+ assert.eq(3, res.cursor.firstBatch.length);
+ assert(arrayEq(
+ [
+ {key: {"_id": 1}, name: "_id_"},
+ {key: {a: 1}, name: "indexA"},
+ {key: {b: 1}, name: "indexB"}
+ ],
+ getIndexesKeyAndName(res.cursor.firstBatch)));
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, res.cursor.ns);
+
+ // Drop those new created indexes.
+ res = assert.commandWorked(
+ tokenDB.runCommand({dropIndexes: kCollName, index: ["indexA", "indexB"]}));
+
+ res = assert.commandWorked(tokenDB.runCommand({listIndexes: kCollName}));
+ assert.eq(1, res.cursor.firstBatch.length);
+ assert(arrayEq([{key: {"_id": 1}, name: "_id_"}],
+ getIndexesKeyAndName(res.cursor.firstBatch)));
}
- // Test commands using a privleged user with ActionType::useTenant and check the user can still
- // run commands on the doc when passing the correct tenant, but not when passing a different
- // tenant.
+ // Test the validate command.
{
- const privelegedConn = new Mongo(primary.host);
- assert(privelegedConn.getDB('admin').auth('admin', 'pwd'));
- const privelegedDB = privelegedConn.getDB(kDbName);
-
- // Find and modify the document using $tenant.
- {
- const fadCorrectDollarTenant = assert.commandWorked(privelegedDB.runCommand({
- findAndModify: kCollName,
- query: {b: 1},
- update: {$inc: {b: 10}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fadCorrectDollarTenant.value);
-
- const fadOtherDollarTenant = assert.commandWorked(privelegedDB.runCommand({
- findAndModify: kCollName,
- query: {b: 11},
- update: {$inc: {b: 10}},
- '$tenant': kOtherTenant
- }));
- assert.eq(null, fadOtherDollarTenant.value);
-
- // Reset document data.
- assert.commandWorked(privelegedDB.runCommand({
- findAndModify: kCollName,
- query: {b: 11},
- update: {$set: {a: 1, b: 1}},
- '$tenant': kTenant
- }));
- }
-
- // Rename the collection using $tenant.
- {
- const fromName = kDbName + '.' + kCollName;
- const toName = fromName + "_renamed";
- const privelegedAdminDB = privelegedConn.getDB('admin');
- assert.commandWorked(privelegedAdminDB.runCommand(
- {renameCollection: fromName, to: toName, dropTarget: true, '$tenant': kTenant}));
-
- // Verify the the renamed collection by findAndModify existing documents.
- const fad1 = assert.commandWorked(privelegedDB.runCommand({
- findAndModify: kCollName + "_renamed",
- query: {a: 1},
- update: {$set: {a: 11, b: 1}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
-
- // Reset the collection name and document data.
- assert.commandWorked(privelegedAdminDB.runCommand(
- {renameCollection: toName, to: fromName, dropTarget: true, '$tenant': kTenant}));
- assert.commandWorked(privelegedDB.runCommand({
- findAndModify: kCollName,
- query: {a: 11},
- update: {$set: {a: 1, b: 1}},
- '$tenant': kTenant
- }));
- }
+ const validateRes = assert.commandWorked(tokenDB.runCommand({validate: kCollName}));
+ assert(validateRes.valid);
+ checkNsSerializedCorrectly(
+ featureFlagRequireTenantId, kTenant, kDbName, kCollName, validateRes.ns);
}
+}
+
+// Test commands using a security token for a different tenant and check that this tenant cannot
+// access the other tenant's collection.
+{
+ // Create a user for a different tenant, and set the security token on the connection.
+ // We reuse the same connection, but swap the token out.
+ assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant2",
+ '$tenant': kOtherTenant,
+ roles:
+ [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+ }));
+ const securityTokenOtherTenant =
+ _createSecurityToken({user: "userTenant2", db: '$external', tenant: kOtherTenant});
+ tokenConn._setSecurityToken(securityTokenOtherTenant);
+
+ const tokenDB2 = tokenConn.getDB(kDbName);
+
+ const findOtherUser =
+ assert.commandWorked(tokenDB2.runCommand({find: kCollName, filter: {a: 1}}));
+ assert.eq(findOtherUser.cursor.firstBatch.length, 0, tojson(findOtherUser));
+
+ const explainOtherUser =
+ assert.commandWorked(tokenDB2.runCommand({explain: {find: kCollName, filter: {a: 1}}}));
+ assert.eq(explainOtherUser.executionStats.nReturned, 0, tojson(explainOtherUser));
+
+ const fadOtherUser = assert.commandWorked(
+ tokenDB2.runCommand({findAndModify: kCollName, query: {b: 1}, update: {$inc: {b: 10}}}));
+ assert.eq(null, fadOtherUser.value);
+
+ const countOtherUser =
+ assert.commandWorked(tokenDB2.runCommand({count: kCollName, query: {c: 1}}));
+ assert.eq(0, countOtherUser.n);
+
+ const distinctOtherUer =
+ assert.commandWorked(tokenDB2.runCommand({distinct: kCollName, key: 'd', query: {}}));
+ assert.eq([], distinctOtherUer.values);
+
+ const fromName = kDbName + '.' + kCollName;
+ const toName = fromName + "_renamed";
+ assert.commandFailedWithCode(tokenConn.getDB("admin").runCommand(
+ {renameCollection: fromName, to: toName, dropTarget: true}),
+ ErrorCodes.NamespaceNotFound);
+
+ assert.commandFailedWithCode(tokenDB2.runCommand({listIndexes: kCollName}),
+ ErrorCodes.NamespaceNotFound);
+ assert.commandFailedWithCode(
+ tokenDB2.runCommand({dropIndexes: kCollName, index: ["indexA", "indexB"]}),
+ ErrorCodes.NamespaceNotFound);
+
+ assert.commandFailedWithCode(tokenDB2.runCommand({validate: kCollName}),
+ ErrorCodes.NamespaceNotFound);
+
+ // ListDatabases with securityToken of kOtherTenant cannot access databases created by
+ // kTenant.
+ const dbsWithDiffToken = assert.commandWorked(
+ tokenConn.getDB('admin').runCommand({listDatabases: 1, nameOnly: true}));
+ // Only the 'admin' db exists
+ assert.eq(1, dbsWithDiffToken.databases.length);
+ const expectedAdminDb = featureFlagRequireTenantId ? "admin" : kOtherTenant + "_admin";
+ assert(arrayEq([expectedAdminDb], dbsWithDiffToken.databases.map(db => db.name)));
+
+ // Attempt to drop the database, then check it was not dropped.
+ assert.commandWorked(tokenDB2.runCommand({dropDatabase: 1}));
+
+ // Run listCollections using the original user's security token to see the collection
+ // exists.
+ tokenConn._setSecurityToken(securityToken);
+ const collsAfterDropOtherTenant =
+ assert.commandWorked(tokenDB.runCommand({listCollections: 1, nameOnly: true}));
+ assert.eq(1,
+ collsAfterDropOtherTenant.cursor.firstBatch.length,
+ tojson(collsAfterDropOtherTenant.cursor.firstBatch));
+}
+
+// Test commands using a privleged user with ActionType::useTenant and check the user can still
+// run commands on the doc when passing the correct tenant, but not when passing a different
+// tenant.
+{
+ const privelegedConn = new Mongo(primary.host);
+ assert(privelegedConn.getDB('admin').auth('admin', 'pwd'));
+ const privelegedDB = privelegedConn.getDB(kDbName);
- // Test collMod
+ // Find and modify the document using $tenant.
{
- // Create the index used for collMod
- let res = assert.commandWorked(tokenDB.runCommand({
- createIndexes: kCollName,
- indexes: [{key: {c: 1}, name: "indexC", expireAfterSeconds: 50}]
+ const fadCorrectDollarTenant = assert.commandWorked(privelegedDB.runCommand({
+ findAndModify: kCollName,
+ query: {b: 1},
+ update: {$inc: {b: 10}},
+ '$tenant': kTenant
}));
- assert.eq(2, res.numIndexesAfter);
- jsTestLog(`Created index`);
-
- // Modify the index with the tenantId
- res = assert.commandWorked(tokenDB.runCommand(
- {"collMod": kCollName, "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100}}));
- assert.eq(50, res.expireAfterSeconds_old);
- assert.eq(100, res.expireAfterSeconds_new);
+ assert.eq({_id: 0, a: 1, b: 1}, fadCorrectDollarTenant.value);
- // Drop the index created
- assert.commandWorked(tokenDB.runCommand({dropIndexes: kCollName, index: ["indexC"]}));
+ const fadOtherDollarTenant = assert.commandWorked(privelegedDB.runCommand({
+ findAndModify: kCollName,
+ query: {b: 11},
+ update: {$inc: {b: 10}},
+ '$tenant': kOtherTenant
+ }));
+ assert.eq(null, fadOtherDollarTenant.value);
+
+ // Reset document data.
+ assert.commandWorked(privelegedDB.runCommand({
+ findAndModify: kCollName,
+ query: {b: 11},
+ update: {$set: {a: 1, b: 1}},
+ '$tenant': kTenant
+ }));
}
- // Test dbCheck command.
- // This should fail since dbCheck is not supporting using a security token.
+ // Rename the collection using $tenant.
{
- assert.commandFailedWithCode(tokenDB.runCommand({dbCheck: kCollName}),
- ErrorCodes.Unauthorized);
+ const fromName = kDbName + '.' + kCollName;
+ const toName = fromName + "_renamed";
+ const privelegedAdminDB = privelegedConn.getDB('admin');
+ assert.commandWorked(privelegedAdminDB.runCommand(
+ {renameCollection: fromName, to: toName, dropTarget: true, '$tenant': kTenant}));
+
+ // Verify the the renamed collection by findAndModify existing documents.
+ const fad1 = assert.commandWorked(privelegedDB.runCommand({
+ findAndModify: kCollName + "_renamed",
+ query: {a: 1},
+ update: {$set: {a: 11, b: 1}},
+ '$tenant': kTenant
+ }));
+ assert.eq({_id: 0, a: 1, b: 1}, fad1.value);
+
+ // Reset the collection name and document data.
+ assert.commandWorked(privelegedAdminDB.runCommand(
+ {renameCollection: toName, to: fromName, dropTarget: true, '$tenant': kTenant}));
+ assert.commandWorked(privelegedDB.runCommand({
+ findAndModify: kCollName,
+ query: {a: 11},
+ update: {$set: {a: 1, b: 1}},
+ '$tenant': kTenant
+ }));
}
+}
- rst.stopSet();
+// Test collMod
+{
+ // Create the index used for collMod
+ let res = assert.commandWorked(tokenDB.runCommand({
+ createIndexes: kCollName,
+ indexes: [{key: {c: 1}, name: "indexC", expireAfterSeconds: 50}]
+ }));
+ assert.eq(2, res.numIndexesAfter);
+ jsTestLog(`Created index`);
+
+ // Modify the index with the tenantId
+ res = assert.commandWorked(tokenDB.runCommand(
+ {"collMod": kCollName, "index": {"keyPattern": {c: 1}, expireAfterSeconds: 100}}));
+ assert.eq(50, res.expireAfterSeconds_old);
+ assert.eq(100, res.expireAfterSeconds_new);
+
+ // Drop the index created
+ assert.commandWorked(tokenDB.runCommand({dropIndexes: kCollName, index: ["indexC"]}));
}
-runTest(true);
-runTest(false);
+
+// Test dbCheck command.
+// This should fail since dbCheck is not supporting using a security token.
+{ assert.commandFailedWithCode(tokenDB.runCommand({dbCheck: kCollName}), ErrorCodes.Unauthorized); }
+
+rst.stopSet();
})();
diff --git a/jstests/serverless/native_tenant_data_isolation_curr_op.js b/jstests/serverless/native_tenant_data_isolation_curr_op.js
index 80d82ecf113..d4652e3bf4b 100644
--- a/jstests/serverless/native_tenant_data_isolation_curr_op.js
+++ b/jstests/serverless/native_tenant_data_isolation_curr_op.js
@@ -1,11 +1,12 @@
// Test that currentOp works as expected in a multitenant environment.
-// @tags: [requires_fcv_62, featureFlagRequireTenantID]
+// @tags: [requires_fcv_62]
(function() {
"use strict";
load('jstests/aggregation/extras/utils.js'); // For arrayEq()
load("jstests/libs/fail_point_util.js"); // For configureFailPoint()
load("jstests/libs/parallel_shell_helpers.js"); // For funWithArgs()
+load("jstests/libs/feature_flag_util.js"); // for isEnabled
const kTenant = ObjectId();
const kOtherTenant = ObjectId();
@@ -90,139 +91,124 @@ function checkNsSerializedCorrectly(
}
}
-function runTest(featureFlagRequireTenantId) {
- const rst = new ReplSetTest({
- nodes: 1,
- nodeOptions: {
- auth: '',
- setParameter: {
- multitenancySupport: true,
- featureFlagSecurityToken: true,
- featureFlagRequireTenantID: featureFlagRequireTenantId
- }
+const rst = new ReplSetTest({
+ nodes: 1,
+ nodeOptions: {
+ auth: '',
+ setParameter: {
+ multitenancySupport: true,
+ featureFlagSecurityToken: true,
}
- });
- rst.startSet({keyFile: 'jstests/libs/key1'});
- rst.initiate();
-
- const primary = rst.getPrimary();
- const adminDb = primary.getDB('admin');
-
- // Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
- assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
- assert(adminDb.auth('admin', 'pwd'));
-
- // Create a non-privileged user for later use.
- assert.commandWorked(
- adminDb.runCommand({createUser: 'dbAdmin', pwd: 'pwd', roles: ['dbAdminAnyDatabase']}));
-
- const securityToken =
- _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant});
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant1",
- '$tenant': kTenant,
- roles:
- [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
- }));
-
- const securityTokenOtherTenant =
- _createSecurityToken({user: "userTenant2", db: '$external', tenant: kOtherTenant});
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant2",
- '$tenant': kOtherTenant,
- roles:
- [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
- }));
-
- // Set the security token for kTenant on the connection to start.
- const tokenConn = new Mongo(primary.host);
- tokenConn._setSecurityToken(securityToken);
-
- // Run an insert for kTenant that will implicitly create a collection, and force the collection
- // creation to hang so that we'll capture it in the currentOp output. Turn on a failpoint to
- // force the create to hang, and wait to hit the failpoint.
- const createCollFP = configureFailPoint(primary, "hangBeforeLoggingCreateCollection");
- function runCreate(securityToken, dbName, kNewCollectionName) {
- db.getMongo()._setSecurityToken(securityToken);
- assert.commandWorked(db.getSiblingDB(dbName).runCommand(
- {insert: kNewCollectionName, documents: [{_id: 0}]}));
}
- const createShell = startParallelShell(
- funWithArgs(runCreate, securityToken, kDbName, kNewCollectionName), primary.port);
- createCollFP.wait();
-
- // Check that the 'insert' op shows up in the currOp output for 'kTenant' when issuing
- // '$currentOp' in aggregation pipeline.
- assertCurrentOpAggOutput(tokenConn,
- adminDb,
- kTenant,
- kDbName,
- 1 /* expectedBatchSize */,
- featureFlagRequireTenantId);
-
- // Check that the 'insert' op shows up in the currOp output for 'kTenant' when issuing
- // the currentOp command.
- assertCurrentOpCommandOutput(tokenConn,
- adminDb,
- kTenant,
- kDbName,
- 1 /* expectedBatchSize */,
- featureFlagRequireTenantId);
-
- // Check that the other tenant does not see the op in currentOp output.
- tokenConn._setSecurityToken(securityTokenOtherTenant);
- assertCurrentOpAggOutput(tokenConn,
+});
+rst.startSet({keyFile: 'jstests/libs/key1'});
+rst.initiate();
+
+const primary = rst.getPrimary();
+const adminDb = primary.getDB('admin');
+
+// Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
+assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+assert(adminDb.auth('admin', 'pwd'));
+
+const featureFlagRequireTenantId = FeatureFlagUtil.isEnabled(adminDb, "RequireTenantID");
+
+// Create a non-privileged user for later use.
+assert.commandWorked(
+ adminDb.runCommand({createUser: 'dbAdmin', pwd: 'pwd', roles: ['dbAdminAnyDatabase']}));
+
+const securityToken = _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant});
+assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant1",
+ '$tenant': kTenant,
+ roles: [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+}));
+
+const securityTokenOtherTenant =
+ _createSecurityToken({user: "userTenant2", db: '$external', tenant: kOtherTenant});
+assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant2",
+ '$tenant': kOtherTenant,
+ roles: [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+}));
+
+// Set the security token for kTenant on the connection to start.
+const tokenConn = new Mongo(primary.host);
+tokenConn._setSecurityToken(securityToken);
+
+// Run an insert for kTenant that will implicitly create a collection, and force the collection
+// creation to hang so that we'll capture it in the currentOp output. Turn on a failpoint to
+// force the create to hang, and wait to hit the failpoint.
+const createCollFP = configureFailPoint(primary, "hangBeforeLoggingCreateCollection");
+function runCreate(securityToken, dbName, kNewCollectionName) {
+ db.getMongo()._setSecurityToken(securityToken);
+ assert.commandWorked(
+ db.getSiblingDB(dbName).runCommand({insert: kNewCollectionName, documents: [{_id: 0}]}));
+}
+const createShell = startParallelShell(
+ funWithArgs(runCreate, securityToken, kDbName, kNewCollectionName), primary.port);
+createCollFP.wait();
+
+// Check that the 'insert' op shows up in the currOp output for 'kTenant' when issuing
+// '$currentOp' in aggregation pipeline.
+assertCurrentOpAggOutput(
+ tokenConn, adminDb, kTenant, kDbName, 1 /* expectedBatchSize */, featureFlagRequireTenantId);
+
+// Check that the 'insert' op shows up in the currOp output for 'kTenant' when issuing
+// the currentOp command.
+assertCurrentOpCommandOutput(
+ tokenConn, adminDb, kTenant, kDbName, 1 /* expectedBatchSize */, featureFlagRequireTenantId);
+
+// Check that the other tenant does not see the op in currentOp output.
+tokenConn._setSecurityToken(securityTokenOtherTenant);
+assertCurrentOpAggOutput(tokenConn,
+ adminDb,
+ kOtherTenant,
+ kDbName,
+ 0 /* expectedBatchSize */,
+ featureFlagRequireTenantId);
+assertCurrentOpCommandOutput(tokenConn,
adminDb,
kOtherTenant,
kDbName,
0 /* expectedBatchSize */,
featureFlagRequireTenantId);
- assertCurrentOpCommandOutput(tokenConn,
- adminDb,
- kOtherTenant,
- kDbName,
- 0 /* expectedBatchSize */,
- featureFlagRequireTenantId);
-
- // Now check that a privileged user can see this op using both $currentOp and the currentOp
- // command when no tenantId is provided.
- const currOpAggResPrivilegedUser = adminDb.runCommand({
- aggregate: 1,
- pipeline: [{$currentOp: {allUsers: true}}, {$match: {op: "insert"}}],
- cursor: {}
- });
- assert.eq(
- currOpAggResPrivilegedUser.cursor.firstBatch.length, 1, tojson(currOpAggResPrivilegedUser));
-
- const currOpCmdResPrivilegedUser =
- adminDb.runCommand({currentOp: 1, $ownOps: false, $all: true, op: "insert"});
- assert.eq(currOpCmdResPrivilegedUser.inprog.length, 1, tojson(currOpCmdResPrivilegedUser));
-
- // Check that a user without required privileges, i.e. not associated with kTenant, is not
- // authorized to issue '$currentOp' for other users.
- const nonPrivilegedConn = new Mongo(primary.host);
- assert(nonPrivilegedConn.getDB("admin").auth('dbAdmin', 'pwd'));
-
- assert.commandFailedWithCode(nonPrivilegedConn.getDB("admin").runCommand({
- aggregate: 1,
- pipeline: [{$currentOp: {allUsers: true}}, {$match: {op: "insert"}}],
- cursor: {}
- }),
- ErrorCodes.Unauthorized);
-
- // Check that a user without required privileges, i.e. not associated with kTenant, is not
- // authorized to issue the currentOp command for other users.
- assert.commandFailedWithCode(nonPrivilegedConn.getDB("admin").runCommand(
- {currentOp: 1, $ownOps: false, $all: true, op: "insert"}),
- ErrorCodes.Unauthorized);
-
- // Turn the failpoint off and wait for the create to finish.
- createCollFP.off();
- createShell();
-
- rst.stopSet();
-}
-runTest(true);
-runTest(false);
+// Now check that a privileged user can see this op using both $currentOp and the currentOp
+// command when no tenantId is provided.
+const currOpAggResPrivilegedUser = adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$currentOp: {allUsers: true}}, {$match: {op: "insert"}}],
+ cursor: {}
+});
+assert.eq(
+ currOpAggResPrivilegedUser.cursor.firstBatch.length, 1, tojson(currOpAggResPrivilegedUser));
+
+const currOpCmdResPrivilegedUser =
+ adminDb.runCommand({currentOp: 1, $ownOps: false, $all: true, op: "insert"});
+assert.eq(currOpCmdResPrivilegedUser.inprog.length, 1, tojson(currOpCmdResPrivilegedUser));
+
+// Check that a user without required privileges, i.e. not associated with kTenant, is not
+// authorized to issue '$currentOp' for other users.
+const nonPrivilegedConn = new Mongo(primary.host);
+assert(nonPrivilegedConn.getDB("admin").auth('dbAdmin', 'pwd'));
+
+assert.commandFailedWithCode(nonPrivilegedConn.getDB("admin").runCommand({
+ aggregate: 1,
+ pipeline: [{$currentOp: {allUsers: true}}, {$match: {op: "insert"}}],
+ cursor: {}
+}),
+ ErrorCodes.Unauthorized);
+
+// Check that a user without required privileges, i.e. not associated with kTenant, is not
+// authorized to issue the currentOp command for other users.
+assert.commandFailedWithCode(nonPrivilegedConn.getDB("admin").runCommand(
+ {currentOp: 1, $ownOps: false, $all: true, op: "insert"}),
+ ErrorCodes.Unauthorized);
+
+// Turn the failpoint off and wait for the create to finish.
+createCollFP.off();
+createShell();
+
+rst.stopSet();
})();
diff --git a/jstests/serverless/native_tenant_data_isolation_initial_sync.js b/jstests/serverless/native_tenant_data_isolation_initial_sync.js
index 08c35707814..678618facb4 100644
--- a/jstests/serverless/native_tenant_data_isolation_initial_sync.js
+++ b/jstests/serverless/native_tenant_data_isolation_initial_sync.js
@@ -6,131 +6,122 @@
"use strict";
load('jstests/aggregation/extras/utils.js'); // For arrayEq()
+load("jstests/libs/feature_flag_util.js"); // for isEnabled
-function runTest(featureFlagRequireTenantId) {
- const rst = new ReplSetTest({
- nodes: 1,
- nodeOptions: {
- auth: '',
- setParameter: {
- multitenancySupport: true,
- featureFlagSecurityToken: true,
- featureFlagRequireTenantID: featureFlagRequireTenantId
- }
- }
- });
- rst.startSet({keyFile: 'jstests/libs/key1'});
- rst.initiate();
-
- const primary = rst.getPrimary();
-
- const kTenant1 = ObjectId();
- const kTenant2 = ObjectId();
- const kDbName = "test";
- const kCollName = "foo";
-
- const primaryConn = new Mongo(primary.host);
- const primaryDB = primaryConn.getDB(kDbName);
-
- // Create users for two different tenants.
- const adminDb = primary.getDB('admin');
- assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
- assert(adminDb.auth('admin', 'pwd'));
-
- const securityToken1 =
- _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant1});
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant1",
- '$tenant': kTenant1,
- roles:
- [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
- }));
-
- const securityToken2 =
- _createSecurityToken({user: "userTenant2", db: '$external', tenant: kTenant2});
- assert.commandWorked(primary.getDB('$external').runCommand({
- createUser: "userTenant2",
- '$tenant': kTenant2,
- roles:
- [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
- }));
-
- // Logout the root user to avoid multiple authentication.
- primaryConn.getDB("admin").logout();
-
- // Create a collection, insert some data, and create indexes on the collection for tenant1.
- primaryConn._setSecurityToken(securityToken1);
-
- const tenant1Docs = [{_id: 0, a: 1, b: 1}, {_id: 1, a: 2, b: 3}];
- assert.commandWorked(primaryDB.runCommand({insert: kCollName, documents: tenant1Docs}));
-
- const tenant1Idxs = [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}];
- let res = assert.commandWorked(
- primaryDB.runCommand({createIndexes: kCollName, indexes: tenant1Idxs}));
- assert.eq(3, res.numIndexesAfter);
-
- // Create a collections, insert some data, and create indexes on the collection for tenant2.
- primaryConn._setSecurityToken(securityToken2);
-
- const tenant2Docs = [{_id: 10, a: 10, b: 10}, {_id: 11, a: 20, b: 30}];
- assert.commandWorked(primaryDB.runCommand({insert: kCollName, documents: tenant2Docs}));
-
- const tenant2Idxs = [{key: {a: -1}, name: "indexA"}, {key: {b: -1}, name: "indexB"}];
- res = assert.commandWorked(
- primaryDB.runCommand({createIndexes: kCollName, indexes: tenant2Idxs}));
- assert.eq(3, res.numIndexesAfter);
-
- // Add a new secondary to the replica set and wait for initial sync to finish.
- const secondary = rst.add({
+const rst = new ReplSetTest({
+ nodes: 1,
+ nodeOptions: {
+ auth: '',
setParameter: {
multitenancySupport: true,
featureFlagSecurityToken: true,
- featureFlagRequireTenantID: featureFlagRequireTenantId
}
- });
- rst.reInitiate();
- rst.awaitReplication();
- rst.awaitSecondaryNodes();
-
- // Check that we find the data for each tenant on the newly synced secondary.
- const secondaryConn = new Mongo(secondary.host);
- const secondaryDB = secondaryConn.getDB(kDbName);
- secondaryConn.setSecondaryOk();
-
- // Look for tenant1's data and indexes.
- secondaryConn._setSecurityToken(securityToken1);
-
- const findTenant1Res =
- assert.commandWorked(secondaryDB.runCommand({find: kCollName, filter: {}}));
- assert(arrayEq(tenant1Docs, findTenant1Res.cursor.firstBatch), tojson(findTenant1Res));
-
- res = assert.commandWorked(secondaryDB.runCommand({listIndexes: kCollName}));
- assert.eq(3, res.cursor.firstBatch.length);
- assert(arrayEq(tenant1Idxs.concat([
- {key: {"_id": 1}, name: "_id_"},
- ]),
- res.cursor.firstBatch.map(function(index) {
- return {key: index.key, name: index.name};
- })));
-
- // Look for tenant2's data and indexes.
- secondaryConn._setSecurityToken(securityToken2);
- const findTenant2Res =
- assert.commandWorked(secondaryDB.runCommand({find: kCollName, filter: {}}));
- assert(arrayEq(tenant2Docs, findTenant2Res.cursor.firstBatch), tojson(findTenant2Res));
-
- res = assert.commandWorked(secondaryDB.runCommand({listIndexes: kCollName}));
- assert.eq(3, res.cursor.firstBatch.length);
- assert(arrayEq(tenant2Idxs.concat([
- {key: {"_id": 1}, name: "_id_"},
- ]),
- res.cursor.firstBatch.map(function(index) {
- return {key: index.key, name: index.name};
- })));
-
- rst.stopSet();
-}
-
-runTest(true);
-runTest(false);
+ }
+});
+rst.startSet({keyFile: 'jstests/libs/key1'});
+rst.initiate();
+
+const primary = rst.getPrimary();
+
+const kTenant1 = ObjectId();
+const kTenant2 = ObjectId();
+const kDbName = "test";
+const kCollName = "foo";
+
+const primaryConn = new Mongo(primary.host);
+const primaryDB = primaryConn.getDB(kDbName);
+
+// Create users for two different tenants.
+const adminDb = primary.getDB('admin');
+assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+assert(adminDb.auth('admin', 'pwd'));
+
+const featureFlagRequireTenantId = FeatureFlagUtil.isEnabled(adminDb, "RequireTenantID");
+
+const securityToken1 =
+ _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant1});
+assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant1",
+ '$tenant': kTenant1,
+ roles: [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+}));
+
+const securityToken2 =
+ _createSecurityToken({user: "userTenant2", db: '$external', tenant: kTenant2});
+assert.commandWorked(primary.getDB('$external').runCommand({
+ createUser: "userTenant2",
+ '$tenant': kTenant2,
+ roles: [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
+}));
+
+// Logout the root user to avoid multiple authentication.
+primaryConn.getDB("admin").logout();
+
+// Create a collection, insert some data, and create indexes on the collection for tenant1.
+primaryConn._setSecurityToken(securityToken1);
+
+const tenant1Docs = [{_id: 0, a: 1, b: 1}, {_id: 1, a: 2, b: 3}];
+assert.commandWorked(primaryDB.runCommand({insert: kCollName, documents: tenant1Docs}));
+
+const tenant1Idxs = [{key: {a: 1}, name: "indexA"}, {key: {b: 1}, name: "indexB"}];
+let res =
+ assert.commandWorked(primaryDB.runCommand({createIndexes: kCollName, indexes: tenant1Idxs}));
+assert.eq(3, res.numIndexesAfter);
+
+// Create a collections, insert some data, and create indexes on the collection for tenant2.
+primaryConn._setSecurityToken(securityToken2);
+
+const tenant2Docs = [{_id: 10, a: 10, b: 10}, {_id: 11, a: 20, b: 30}];
+assert.commandWorked(primaryDB.runCommand({insert: kCollName, documents: tenant2Docs}));
+
+const tenant2Idxs = [{key: {a: -1}, name: "indexA"}, {key: {b: -1}, name: "indexB"}];
+res = assert.commandWorked(primaryDB.runCommand({createIndexes: kCollName, indexes: tenant2Idxs}));
+assert.eq(3, res.numIndexesAfter);
+
+// Add a new secondary to the replica set and wait for initial sync to finish.
+const secondary = rst.add({
+ setParameter: {
+ multitenancySupport: true,
+ featureFlagSecurityToken: true,
+ }
+});
+rst.reInitiate();
+rst.awaitReplication();
+rst.awaitSecondaryNodes();
+
+// Check that we find the data for each tenant on the newly synced secondary.
+const secondaryConn = new Mongo(secondary.host);
+const secondaryDB = secondaryConn.getDB(kDbName);
+secondaryConn.setSecondaryOk();
+
+// Look for tenant1's data and indexes.
+secondaryConn._setSecurityToken(securityToken1);
+
+const findTenant1Res = assert.commandWorked(secondaryDB.runCommand({find: kCollName, filter: {}}));
+assert(arrayEq(tenant1Docs, findTenant1Res.cursor.firstBatch), tojson(findTenant1Res));
+
+res = assert.commandWorked(secondaryDB.runCommand({listIndexes: kCollName}));
+assert.eq(3, res.cursor.firstBatch.length);
+assert(arrayEq(tenant1Idxs.concat([
+ {key: {"_id": 1}, name: "_id_"},
+]),
+ res.cursor.firstBatch.map(function(index) {
+ return {key: index.key, name: index.name};
+ })));
+
+// Look for tenant2's data and indexes.
+secondaryConn._setSecurityToken(securityToken2);
+const findTenant2Res = assert.commandWorked(secondaryDB.runCommand({find: kCollName, filter: {}}));
+assert(arrayEq(tenant2Docs, findTenant2Res.cursor.firstBatch), tojson(findTenant2Res));
+
+res = assert.commandWorked(secondaryDB.runCommand({listIndexes: kCollName}));
+assert.eq(3, res.cursor.firstBatch.length);
+assert(arrayEq(tenant2Idxs.concat([
+ {key: {"_id": 1}, name: "_id_"},
+]),
+ res.cursor.firstBatch.map(function(index) {
+ return {key: index.key, name: index.name};
+ })));
+
+rst.stopSet();
})();
diff --git a/jstests/serverless/native_tenant_data_isolation_stop_restart.js b/jstests/serverless/native_tenant_data_isolation_stop_restart.js
index dd53b4d7f2c..10731785a4a 100644
--- a/jstests/serverless/native_tenant_data_isolation_stop_restart.js
+++ b/jstests/serverless/native_tenant_data_isolation_stop_restart.js
@@ -5,95 +5,80 @@
"use strict";
load('jstests/aggregation/extras/utils.js'); // For arrayEq()
+load("jstests/libs/feature_flag_util.js"); // for isEnabled
-function runTest(featureFlagRequireTenantId) {
- const rst = new ReplSetTest({
- nodes: 3,
- nodeOptions: {
- auth: '',
- setParameter:
- {multitenancySupport: true, featureFlagRequireTenantID: featureFlagRequireTenantId}
- }
- });
- rst.startSet({keyFile: 'jstests/libs/key1'});
- rst.initiate();
-
- let primary = rst.getPrimary();
- let adminDb = primary.getDB('admin');
-
- // Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
- assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+const rst =
+ new ReplSetTest({nodes: 3, nodeOptions: {auth: '', setParameter: {multitenancySupport: true}}});
+rst.startSet({keyFile: 'jstests/libs/key1'});
+rst.initiate();
+
+let primary = rst.getPrimary();
+let adminDb = primary.getDB('admin');
+
+// Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
+assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
+assert(adminDb.auth('admin', 'pwd'));
+
+const featureFlagRequireTenantId = FeatureFlagUtil.isEnabled(adminDb, "RequireTenantID");
+
+{
+ const kTenant = ObjectId();
+ let testDb = primary.getDB('myDb0');
+
+ // Create a collection by inserting a document to it.
+ assert.commandWorked(testDb.runCommand(
+ {insert: 'myColl0', documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
+
+ // Run findAndModify on the document.
+ let fad = assert.commandWorked(testDb.runCommand(
+ {findAndModify: "myColl0", query: {a: 1}, update: {$inc: {a: 10}}, '$tenant': kTenant}));
+ assert.eq({_id: 0, a: 1, b: 1}, fad.value);
+
+ // Create a view on the collection.
+ assert.commandWorked(testDb.runCommand(
+ {"create": "view1", "viewOn": "myColl0", pipeline: [], '$tenant': kTenant}));
+
+ // Stop the rs and restart it.
+ rst.stopSet(null /* signal */, true /* forRestart */, {noCleanData: true});
+ rst.startSet({restart: true});
+ primary = rst.getPrimary();
+
+ adminDb = primary.getDB('admin');
assert(adminDb.auth('admin', 'pwd'));
+ testDb = primary.getDB('myDb0');
- {
- const kTenant = ObjectId();
- let testDb = primary.getDB('myDb0');
-
- // Create a collection by inserting a document to it.
- assert.commandWorked(testDb.runCommand(
- {insert: 'myColl0', documents: [{_id: 0, a: 1, b: 1}], '$tenant': kTenant}));
-
- // Run findAndModify on the document.
- let fad = assert.commandWorked(testDb.runCommand({
- findAndModify: "myColl0",
- query: {a: 1},
- update: {$inc: {a: 10}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 1, b: 1}, fad.value);
-
- // Create a view on the collection.
- assert.commandWorked(testDb.runCommand(
- {"create": "view1", "viewOn": "myColl0", pipeline: [], '$tenant': kTenant}));
-
- // Stop the rs and restart it.
- rst.stopSet(null /* signal */, true /* forRestart */, {noCleanData: true});
- rst.startSet({restart: true});
- primary = rst.getPrimary();
-
- adminDb = primary.getDB('admin');
- assert(adminDb.auth('admin', 'pwd'));
- testDb = primary.getDB('myDb0');
-
- // Assert we see 3 collections in the tenant's db 'myDb0' - the original collection we
- // created, the view on it, and the system.views collection.
- const colls = assert.commandWorked(
- testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kTenant}));
- assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
- const expectedColls = [
- {"name": "myColl0", "type": "collection"},
- {"name": "system.views", "type": "collection"},
- {"name": "view1", "type": "view"}
- ];
- assert(arrayEq(expectedColls, colls.cursor.firstBatch), tojson(colls.cursor.firstBatch));
-
- // Assert we can still run findAndModify on the doc.
- fad = assert.commandWorked(testDb.runCommand({
- findAndModify: "myColl0",
- query: {a: 11},
- update: {$inc: {a: 10}},
- '$tenant': kTenant
- }));
- assert.eq({_id: 0, a: 11, b: 1}, fad.value);
-
- const findAndModPrefixed =
- primary.getDB(kTenant + '_myDb0')
- .runCommand({findAndModify: "myColl0", query: {b: 1}, update: {$inc: {b: 10}}});
- if (!featureFlagRequireTenantId) {
- // Check that we do find the doc when the tenantId was passed as a prefix, only if the
- // feature flag is not enabled. In this case, the server still accepts prefixed names,
- // and will parse the tenant from the db name.
- assert.commandWorked(findAndModPrefixed);
- assert.eq({_id: 0, a: 21, b: 1}, findAndModPrefixed.value);
- } else {
- // assert.commandFailed(findAndModPrefixed);
- // TODO SERVER-70876 Uncomment out the check above, and remove the check below.
- assert.eq(null, findAndModPrefixed.value);
- }
- }
+ // Assert we see 3 collections in the tenant's db 'myDb0' - the original collection we
+ // created, the view on it, and the system.views collection.
+ const colls = assert.commandWorked(
+ testDb.runCommand({listCollections: 1, nameOnly: true, '$tenant': kTenant}));
+ assert.eq(3, colls.cursor.firstBatch.length, tojson(colls.cursor.firstBatch));
+ const expectedColls = [
+ {"name": "myColl0", "type": "collection"},
+ {"name": "system.views", "type": "collection"},
+ {"name": "view1", "type": "view"}
+ ];
+ assert(arrayEq(expectedColls, colls.cursor.firstBatch), tojson(colls.cursor.firstBatch));
- rst.stopSet();
+ // Assert we can still run findAndModify on the doc.
+ fad = assert.commandWorked(testDb.runCommand(
+ {findAndModify: "myColl0", query: {a: 11}, update: {$inc: {a: 10}}, '$tenant': kTenant}));
+ assert.eq({_id: 0, a: 11, b: 1}, fad.value);
+
+ const findAndModPrefixed =
+ primary.getDB(kTenant + '_myDb0')
+ .runCommand({findAndModify: "myColl0", query: {b: 1}, update: {$inc: {b: 10}}});
+ if (!featureFlagRequireTenantId) {
+ // Check that we do find the doc when the tenantId was passed as a prefix, only if the
+ // feature flag is not enabled. In this case, the server still accepts prefixed names,
+ // and will parse the tenant from the db name.
+ assert.commandWorked(findAndModPrefixed);
+ assert.eq({_id: 0, a: 21, b: 1}, findAndModPrefixed.value);
+ } else {
+ // assert.commandFailed(findAndModPrefixed);
+ // TODO SERVER-70876 Uncomment out the check above, and remove the check below.
+ assert.eq(null, findAndModPrefixed.value);
+ }
}
-runTest(true);
-runTest(false);
+
+rst.stopSet();
})();