summaryrefslogtreecommitdiff
path: root/jstests/auth
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2016-09-06 17:21:48 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2016-09-06 17:36:48 -0400
commit1f389ce467330cda1171d2a04bd0e0b2890aaf8d (patch)
tree5daf30e4b8cf3a7106eb374b77589e40fb0f6412 /jstests/auth
parent175408e60358fe0407a4c24e8057a73f0031a9a3 (diff)
downloadmongo-1f389ce467330cda1171d2a04bd0e0b2890aaf8d.tar.gz
SERVER-25526 add views authz tests to auth commands lib
Adds test infrastructure to handle special-case behavior for views when a privilege specifies "removeWhenTestingAuthzFailure: false". Also fixes SERVER-25825.
Diffstat (limited to 'jstests/auth')
-rw-r--r--jstests/auth/commands_user_defined_roles.js68
-rw-r--r--jstests/auth/lib/commands_lib.js643
-rw-r--r--jstests/auth/views_authz.js2
3 files changed, 683 insertions, 30 deletions
diff --git a/jstests/auth/commands_user_defined_roles.js b/jstests/auth/commands_user_defined_roles.js
index 8dff4e050be..dd6abc7d252 100644
--- a/jstests/auth/commands_user_defined_roles.js
+++ b/jstests/auth/commands_user_defined_roles.js
@@ -13,7 +13,10 @@ var testRole = "userDefinedRolesTestRole";
load("jstests/auth/lib/commands_lib.js");
-function testProperAuthorization(conn, t, testcase) {
+/**
+ * Run the command specified in 't' with the privileges specified in 'privileges'.
+ */
+function testProperAuthorization(conn, t, testcase, privileges) {
var out = "";
var runOnDb = conn.getDB(testcase.runOnDb);
@@ -23,8 +26,7 @@ function testProperAuthorization(conn, t, testcase) {
authCommandsLib.setup(conn, t, runOnDb);
adminDb.auth("admin", "password");
- assert.commandWorked(
- adminDb.runCommand({updateRole: testRole, privileges: testcase.privileges}));
+ assert.commandWorked(adminDb.runCommand({updateRole: testRole, privileges: privileges}));
adminDb.logout();
assert(adminDb.auth(testUser, "password"));
@@ -35,10 +37,10 @@ function testProperAuthorization(conn, t, testcase) {
// don't error if the test failed with code commandNotSupported since
// some storage engines (e.g wiredTiger) don't support some commands (e.g. touch)
out = "command failed with " + tojson(res) + " on db " + testcase.runOnDb +
- " with privileges " + tojson(testcase.privileges);
+ " with privileges " + tojson(privileges);
} else if (testcase.expectFail && res.code == authErrCode) {
out = "expected authorization success" + " but received " + tojson(res) + " on db " +
- testcase.runOnDb + " with privileges " + tojson(testcase.privileges);
+ testcase.runOnDb + " with privileges " + tojson(privileges);
}
firstDb.logout();
@@ -82,25 +84,29 @@ function runOneTest(conn, t) {
if (!("privileges" in testcase)) {
continue;
}
- // Make a copy of the priviliges array since it will be modified.
- var privileges = testcase.privileges.map(function(p) {
- return Object.extend({}, p, true);
- });
if (testcase.expectAuthzFailure) {
- msg = testInsufficientPrivileges(conn, t, testcase, privileges);
+ msg = testInsufficientPrivileges(conn, t, testcase, testcase.privileges);
if (msg) {
failures.push(t.testname + ": " + msg);
}
continue;
}
- if ((privileges.length == 1 && privileges[0].actions.length > 1) || privileges.length > 1) {
- for (var j = 0; j < privileges.length; j++) {
- var p = privileges[j];
+ if ((testcase.privileges.length == 1 && testcase.privileges[0].actions.length > 1) ||
+ testcase.privileges.length > 1) {
+ for (var j = 0; j < testcase.privileges.length; j++) {
+ var p = testcase.privileges[j];
var resource = p.resource;
var actions = p.actions;
+ // A particular privilege can explicitly specify that it should not be removed when
+ // testing for authorization failure. This accommodates special-case behavior for
+ // views in conjunction with the create and collMod commands.
+ if (p.removeWhenTestingAuthzFailure === false) {
+ continue;
+ }
+
for (var k = 0; k < actions.length; k++) {
var privDoc = {resource: resource, actions: [actions[k]]};
msg = testInsufficientPrivileges(conn, t, testcase, [privDoc]);
@@ -111,27 +117,37 @@ function runOneTest(conn, t) {
}
}
- msg = testProperAuthorization(conn, t, testcase);
+ // Test for proper authorization with the privileges specified in the test case.
+ msg = testProperAuthorization(conn, t, testcase, testcase.privileges);
if (msg) {
failures.push(t.testname + ": " + msg);
}
- // test resource pattern where collection is ""
- privileges.forEach(function(j) {
- if (j.resource.collection && !j.resource.collection.startsWith('system.')) {
- j.resource.collection = "";
+
+ // Test for proper authorization with the test case's privileges where non-system
+ // collections are modified to be the empty string.
+ msg = testProperAuthorization(conn, t, testcase, testcase.privileges.map(function(priv) {
+ // Make a copy of the privilege so as not to modify the original array.
+ var modifiedPrivilege = Object.extend({}, priv, true);
+ if (modifiedPrivilege.resource.collection &&
+ !modifiedPrivilege.resource.collection.startsWith('system.')) {
+ modifiedPrivilege.resource.collection = "";
}
- });
- msg = testProperAuthorization(conn, t, testcase);
+ return modifiedPrivilege;
+ }));
if (msg) {
failures.push(t.testname + ": " + msg);
}
- // test resource pattern where database is ""
- privileges.forEach(function(j) {
- if (j.resource.db) {
- j.resource.db = "";
+
+ // Test for proper authorization with the test case's privileges where the database is the
+ // empty string.
+ msg = testProperAuthorization(conn, t, testcase, testcase.privileges.map(function(priv) {
+ // Make a copy of the privilege so as not to modify the original array.
+ var modifiedPrivilege = Object.extend({}, priv, true);
+ if (modifiedPrivilege.resource.db) {
+ modifiedPrivilege.resource.db = "";
}
- });
- msg = testProperAuthorization(conn, t, testcase);
+ return modifiedPrivilege;
+ }));
if (msg) {
failures.push(t.testname + ": " + msg);
}
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 7a6b52c125d..5bc9583f253 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -75,6 +75,14 @@ The teardown function, if present, is called immediately after
testint whether a particular role authorizes a command for a
particular database.
+8) privileges
+
+An array of privileges used when testing user-defined roles. The test case tests that a user with
+the specified privileges is authorized to run the command, and that having only a subset of the
+privileges causes an authorization failure. If an individual privilege specifies
+"removeWhenTestingAuthzFailure: false", then that privilege will not be removed when testing for
+authorization failure.
+
*/
// constants
@@ -235,6 +243,8 @@ var authCommandsLib = {
},
command: {aggregate: "view", pipeline: []},
testcases: [
+ // Tests that a user with read privileges on a view can aggregate it, even if they
+ // don't have read privileges on the underlying namespace.
{
runOnDb: firstDbName,
roles: roles_read,
@@ -275,6 +285,8 @@ var authCommandsLib = {
},
command: {aggregate: "view", explain: true, pipeline: [{$match: {bar: 1}}]},
testcases: [
+ // Tests that a user with read privileges on a view can explain an aggregation on the
+ // view, even if they don't have read privileges on the underlying namespace.
{
runOnDb: firstDbName,
roles: roles_read,
@@ -425,6 +437,42 @@ var authCommandsLib = {
]
},
{
+ testname: "aggregate_lookup_views",
+ setup: function(db) {
+ db.createView("view", "collection", [{$match: {}}]);
+ db.createCollection("foo");
+ },
+ teardown: function(db) {
+ db.view.drop();
+ db.foo.drop();
+ },
+ command: {
+ aggregate: "foo",
+ pipeline: [{
+ $lookup: {from: "view", localField: "_id", foreignField: "_id", as: "results"}
+ }]
+ },
+ testcases: [
+ // Tests that a user can successfully $lookup into a view when given read access.
+ {
+ runOnDb: firstDbName,
+ roles: roles_read,
+ privileges: [
+ {resource: {db: firstDbName, collection: "view"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]}
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: roles_readAny,
+ privileges: [
+ {resource: {db: secondDbName, collection: "view"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]}
+ ]
+ }
+ ]
+ },
+ {
testname: "aggregate_graphLookup",
command: {
aggregate: "foo",
@@ -466,6 +514,48 @@ var authCommandsLib = {
]
},
{
+ testname: "aggregate_graphLookup_views",
+ setup: function(db) {
+ db.createView("view", "collection", [{$match: {}}]);
+ db.createCollection("foo");
+ },
+ teardown: function(db) {
+ db.view.drop();
+ db.foo.drop();
+ },
+ command: {
+ aggregate: "foo",
+ pipeline: [{
+ $graphLookup: {
+ from: "view",
+ startWith: [1],
+ connectFromField: "_id",
+ connectToField: "viewId",
+ as: "results"
+ }
+ }]
+ },
+ testcases: [
+ // Tests that a user can successfully $graphLookup into a view when given read access.
+ {
+ runOnDb: firstDbName,
+ roles: roles_read,
+ privileges: [
+ {resource: {db: firstDbName, collection: "view"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]}
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: roles_readAny,
+ privileges: [
+ {resource: {db: secondDbName, collection: "view"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]}
+ ]
+ }
+ ]
+ },
+ {
testname: "aggregate_collStats",
command: {aggregate: "foo", pipeline: [{$collStats: {latencyStats: {}}}]},
setup: function(db) {
@@ -570,6 +660,75 @@ var authCommandsLib = {
]
},
{
+ testname: "aggregate_facet_views",
+ command: {
+ aggregate: "foo",
+ pipeline: [{
+ $facet: {
+ lookup: [{
+ $lookup: {
+ from: "view1",
+ localField: "_id",
+ foreignField: "_id",
+ as: "results"
+ }
+ }],
+ graphLookup: [{
+ $graphLookup: {
+ from: "view2",
+ startWith: "foo",
+ connectFromField: "_id",
+ connectToField: "_id",
+ as: "results"
+ }
+ }]
+ }
+ }]
+ },
+ setup: function(db) {
+ db.createCollection("foo");
+ db.createView("view1", "bar", [
+ {$lookup: {from: "qux", localField: "_id", foreignField: "_id", as: "results"}}
+ ]);
+ db.createView("view2", "baz", [{
+ $graphLookup: {
+ from: "quz",
+ startWith: [1],
+ connectFromField: "_id",
+ connectToField: "_id",
+ as: "results"
+ }
+ }]);
+ },
+ teardown: function(db) {
+ db.foo.drop();
+ db.view1.drop();
+ db.view2.drop();
+ },
+ testcases: [
+ // Tests that a user can successfully $lookup and $graphLookup into views when the
+ // lookups are nested within a $facet.
+ {
+ runOnDb: firstDbName,
+ roles: roles_read,
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "view1"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "view2"}, actions: ["find"]}
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: roles_readAny,
+ privileges: [
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "view1"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "view2"}, actions: ["find"]}
+ ]
+ }
+ ]
+ },
+ {
testname: "appendOplogNote",
command: {appendOplogNote: 1, data: {a: 1}},
skipSharded: true,
@@ -731,13 +890,15 @@ var authCommandsLib = {
{
testname: "collMod_views",
setup: function(db) {
- db.createView("view", "collection", [{$match: {}}]);
+ db.createView("view", "foo", [{$match: {}}]);
},
teardown: function(db) {
db.view.drop();
},
- command: {collMod: "view", viewOn: "collection2", pipeline: [{$limit: 7}]},
+ command: {collMod: "view", viewOn: "bar", pipeline: [{$limit: 7}]},
testcases: [
+ // Tests that a user who can modify a view (but not read it) may modify it to be a
+ // view on a namespace the user also cannot read.
{
runOnDb: firstDbName,
roles: Object.extend({restore: 1}, roles_dbAdmin),
@@ -749,6 +910,241 @@ var authCommandsLib = {
roles: Object.extend({restore: 1}, roles_dbAdminAny),
privileges:
[{resource: {db: secondDbName, collection: "view"}, actions: ["collMod"]}]
+ },
+ // Tests that a user who can both read and modify a view has read privileges on the
+ // view's underlying namespace.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ }
+ ]
+ },
+ {
+ testname: "collMod_views_lookup",
+ setup: function(db) {
+ db.createView("view", "foo", [{$match: {}}]);
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ command: {
+ collMod: "view",
+ viewOn: "bar",
+ pipeline: [
+ {$lookup: {from: "baz", localField: "_id", foreignField: "_id", as: "results"}}
+ ]
+ },
+ testcases: [
+ // Tests that a user who can modify a view (but not read it) may modify it to depend
+ // on namespaces the user also cannot read, including namespaces accessed via $lookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges:
+ [{resource: {db: firstDbName, collection: "view"}, actions: ["collMod"]}]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges:
+ [{resource: {db: secondDbName, collection: "view"}, actions: ["collMod"]}]
+ },
+ // Tests that a user who can both read and modify a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $lookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ }
+ ]
+ },
+ {
+ testname: "collMod_views_graphLookup",
+ setup: function(db) {
+ db.createView("view", "foo", [{$match: {}}]);
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ command: {
+ collMod: "view",
+ viewOn: "bar",
+ pipeline: [{
+ $graphLookup: {
+ from: "baz",
+ startWith: [1],
+ connectFromField: "_id",
+ connectToField: "bazId",
+ as: "results"
+ }
+ }]
+ },
+ testcases: [
+ // Tests that a user who can modify a view (but not read it) may modify it to depend
+ // on namespaces the user also cannot read, including namespaces accessed via
+ // $graphLookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges:
+ [{resource: {db: firstDbName, collection: "view"}, actions: ["collMod"]}]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges:
+ [{resource: {db: secondDbName, collection: "view"}, actions: ["collMod"]}]
+ },
+ // Tests that a user who can both read and modify a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $graphLookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ }
+ ]
+ },
+ {
+ testname: "collMod_views_facet",
+ setup: function(db) {
+ db.createView("view", "foo", []);
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ command: {
+ collMod: "view",
+ viewOn: "bar",
+ pipeline: [{
+ $facet: {
+ lookup: [{
+ $lookup: {
+ from: "baz",
+ localField: "_id",
+ foreignField: "_id",
+ as: "results"
+ }
+ }],
+ graphLookup: [{
+ $graphLookup: {
+ from: "qux",
+ startWith: "blah",
+ connectFromField: "_id",
+ connectToField: "_id",
+ as: "results"
+ }
+ }]
+ }
+ }]
+ },
+ testcases: [
+ // Tests that a user who can modify a view (but not read it) may modify it to depend
+ // on namespaces the user also cannot read, including namespaces accessed via $lookup
+ // and $graphLookup that are nested within a $facet.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges:
+ [{resource: {db: firstDbName, collection: "view"}, actions: ["collMod"]}],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges:
+ [{resource: {db: secondDbName, collection: "view"}, actions: ["collMod"]}],
+ },
+ // Tests that a user who can both read and modify a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $lookup and
+ // $graphLookup that are nested within a $facet.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "baz"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "qux"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_dbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "baz"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "qux"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["collMod", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
}
]
},
@@ -1030,6 +1426,8 @@ var authCommandsLib = {
db.view.drop();
},
testcases: [
+ // Tests that a user who can create a view (but not read it) can create a view on a
+ // namespace the user also cannot read.
{
runOnDb: firstDbName,
roles: Object.extend({restore: 1}, roles_writeDbAdmin),
@@ -1046,6 +1444,245 @@ var authCommandsLib = {
actions: ["createCollection"]
}]
},
+ // Tests that a user who can both create and read a view has read privileges on the
+ // view's underlying namespace.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "collection"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "collection"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ }
+ ]
+ },
+ {
+ testname: "create_views_lookup",
+ command: {
+ create: "view",
+ viewOn: "foo",
+ pipeline: [
+ {$lookup: {from: "bar", localField: "_id", foreignField: "_id", as: "results"}}
+ ]
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ testcases: [
+ // Tests that a user who can create a view (but not read it) may create a view that
+ // depends on namespaces the user also cannot read, including namespaces accessed via
+ // $lookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [{
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [{
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ // Tests that a user who can both create and read a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $lookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ]
+ }
+ ]
+ },
+ {
+ testname: "create_views_graphLookup",
+ command: {
+ create: "view",
+ viewOn: "foo",
+ pipeline: [{
+ $graphLookup: {
+ from: "bar",
+ startWith: [1],
+ connectFromField: "_id",
+ connectToField: "barId",
+ as: "results"
+ }
+ }]
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ testcases: [
+ // Tests that a user who can create a view (but not read it) may create a view that
+ // depends on namespaces the user also cannot read, including namespaces accessed via
+ // $graphLookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [{
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [{
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ // Tests that a user who can both create and read a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $graphLookup.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
+ }
+ ]
+ },
+ {
+ testname: "create_views_facet",
+ command: {
+ create: "view",
+ viewOn: "foo",
+ pipeline: [{
+ $facet: {
+ lookup: [{
+ $lookup: {
+ from: "bar",
+ localField: "_id",
+ foreignField: "_id",
+ as: "results"
+ }
+ }],
+ graphLookup: [{
+ $graphLookup: {
+ from: "baz",
+ startWith: "foo",
+ connectFromField: "_id",
+ connectToField: "_id",
+ as: "results"
+ }
+ }]
+ }
+ }]
+ },
+ teardown: function(db) {
+ db.view.drop();
+ },
+ testcases: [
+ // Tests that a user who can create a view (but not read it) may create a view that
+ // depends on namespaces the user also cannot read, including namespaces accessed via
+ // $lookup and $graphLookup that are nested within a $facet.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [{
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [{
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection"]
+ }],
+ },
+ // Tests that a user who can both create and read a view has read privileges on all
+ // the view's dependent namespaces, including namespaces accessed via $lookup and
+ // $graphLookup that are nested within a $facet.
+ {
+ runOnDb: firstDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdmin),
+ privileges: [
+ {resource: {db: firstDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: firstDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: firstDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
+ },
+ {
+ runOnDb: secondDbName,
+ roles: Object.extend({restore: 1}, roles_writeDbAdminAny),
+ privileges: [
+ {resource: {db: secondDbName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "bar"}, actions: ["find"]},
+ {resource: {db: secondDbName, collection: "baz"}, actions: ["find"]},
+ {
+ resource: {db: secondDbName, collection: "view"},
+ actions: ["createCollection", "find"],
+ removeWhenTestingAuthzFailure: false
+ }
+ ],
+ }
]
},
{
@@ -1459,6 +2096,8 @@ var authCommandsLib = {
},
command: {find: "view"},
testcases: [
+ // Tests that a user with read access to a view can perform a find on it, even if they
+ // don't have read access to the underlying namespace.
{
runOnDb: firstDbName,
roles: roles_read,
diff --git a/jstests/auth/views_authz.js b/jstests/auth/views_authz.js
index 376e85c7e64..605b166be41 100644
--- a/jstests/auth/views_authz.js
+++ b/jstests/auth/views_authz.js
@@ -1,8 +1,6 @@
/**
* 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.
- *
- * TODO(SERVER-25526): merge this test into jstests/auth/libs/commands_lib.js.
*/
(function() {
"use strict";