Files
mongo/jstests/auth/views_authz.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

161 lines
6.8 KiB
JavaScript

/**
* Tests authorization special cases with views. These are special exceptions that prohibit certain
* operations on views even if the user has an explicit privilege on that view.
* @tags: [requires_sharding]
*/
import {ShardingTest} from "jstests/libs/shardingtest.js";
// Multiple users cannot be authenticated on one connection within a session.
TestData.disableImplicitSessions = true;
function runTest(conn) {
// Create the admin user.
const adminDB = conn.getDB("admin");
assert.commandWorked(adminDB.runCommand({createUser: "admin", pwd: "admin", roles: ["root"]}));
assert(adminDB.auth("admin", "admin"));
const viewsDBName = "views_authz";
const viewsDB = adminDB.getSiblingDB(viewsDBName);
viewsDB.dropAllUsers();
viewsDB.logout();
// Create a user who can read, create and modify a view 'view' and a read a namespace
// 'permitted' but does not have access to 'forbidden'.
assert.commandWorked(
viewsDB.runCommand({
createRole: "readWriteView",
privileges: [
{
resource: {db: viewsDBName, collection: "view"},
actions: ["find", "createCollection", "collMod"],
},
{resource: {db: viewsDBName, collection: "view2"}, actions: ["find"]},
{resource: {db: viewsDBName, collection: "permitted"}, actions: ["find"]},
],
roles: [],
}),
);
assert.commandWorked(viewsDB.runCommand({createUser: "viewUser", pwd: "pwd", roles: ["readWriteView"]}));
adminDB.logout();
assert(viewsDB.auth("viewUser", "pwd"));
const lookupStage = {$lookup: {from: "forbidden", localField: "x", foreignField: "x", as: "y"}};
const graphLookupStage = {
$graphLookup: {from: "forbidden", startWith: [], connectFromField: "x", connectToField: "x", as: "y"},
};
// You cannot create a view if you have both the 'createCollection' and 'find' actions on
// that view but not the 'find' action on all of the dependent namespaces.
assert.commandFailedWithCode(
viewsDB.createView("view", "forbidden", []),
ErrorCodes.Unauthorized,
"created a readable view on an unreadable collection",
);
assert.commandFailedWithCode(
viewsDB.createView("view", "permitted", [lookupStage]),
ErrorCodes.Unauthorized,
"created a readable view on an unreadable collection via $lookup",
);
assert.commandFailedWithCode(
viewsDB.createView("view", "permitted", [graphLookupStage]),
ErrorCodes.Unauthorized,
"created a readable view on an unreadable collection via $graphLookup",
);
assert.commandFailedWithCode(
viewsDB.createView("view", "permitted", [{$facet: {a: [lookupStage]}}]),
ErrorCodes.Unauthorized,
"created a readable view on an unreadable collection via $lookup in a $facet",
);
assert.commandFailedWithCode(
viewsDB.createView("view", "permitted", [{$facet: {b: [graphLookupStage]}}]),
ErrorCodes.Unauthorized,
"created a readable view on an unreadable collection via $graphLookup in a $facet",
);
assert.commandWorked(viewsDB.createView("view", "permitted", [{$match: {x: 1}}]));
// You cannot modify a view if you have both the 'collMod' and 'find' actions on that view
// but not the 'find' action on all of the dependent namespaces.
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "forbidden", pipeline: [{$match: {}}]}),
ErrorCodes.Unauthorized,
"modified a view to read an unreadable collection",
);
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [lookupStage]}),
ErrorCodes.Unauthorized,
"modified a view to read an unreadable collection via $lookup",
);
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [graphLookupStage]}),
ErrorCodes.Unauthorized,
"modified a view to read an unreadable collection via $graphLookup",
);
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [{$facet: {a: [lookupStage]}}]}),
ErrorCodes.Unauthorized,
"modified a view to read an unreadable collection via $lookup in a $facet",
);
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "permitted", pipeline: [{$facet: {b: [graphLookupStage]}}]}),
ErrorCodes.Unauthorized,
"modified a view to read an unreadable collection via $graphLookup in a $facet",
);
// When auth is enabled, users must specify both "viewOn" and "pipeline" when running
// collMod on a view; specifying only one or the other is not allowed. Without both the
// "viewOn" and "pipeline" specified, authorization checks cannot determine if the users
// have the necessary privileges.
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", pipeline: []}),
ErrorCodes.InvalidOptions,
"modified a view without having to specify 'viewOn'",
);
assert.commandFailedWithCode(
viewsDB.runCommand({collMod: "view", viewOn: "other"}),
ErrorCodes.InvalidOptions,
"modified a view without having to specify 'pipeline'",
);
viewsDB.logout();
// Create a view on a forbidden collection and populate it.
assert.eq(1, adminDB.auth("admin", "admin"));
assert.commandWorked(viewsDB.createView("view2", "forbidden", []));
for (let i = 0; i < 10; i++) {
assert.commandWorked(viewsDB.forbidden.insert({x: 1}));
}
adminDB.logout();
// Performing a find on a readable view returns a cursor that allows us to perform a getMore
// even if the underlying collection is unreadable.
assert(viewsDB.auth("viewUser", "pwd"));
assert.commandFailedWithCode(
viewsDB.runCommand({find: "forbidden"}),
ErrorCodes.Unauthorized,
"successfully performed a find on an unreadable namespace",
);
const res = viewsDB.runCommand({find: "view2", batchSize: 1});
assert.commandWorked(res, "could not perform a find on a readable view");
assert.eq(
res.cursor.ns,
"views_authz.view2",
"performing find on a view does not return a cursor on the view namespace",
);
assert.commandWorked(
viewsDB.runCommand({getMore: res.cursor.id, collection: "view2"}),
"could not perform getMore on a readable view",
);
viewsDB.logout();
}
// Run the test on a standalone.
const mongod = MongoRunner.runMongod({auth: "", bind_ip: "127.0.0.1"});
runTest(mongod);
MongoRunner.stopMongod(mongod);
// Run the test on a sharded cluster.
const cluster = new ShardingTest({shards: 1, mongos: 1, keyFile: "jstests/libs/key1", other: {rsOptions: {auth: ""}}});
runTest(cluster);
cluster.stop();