diff options
author | Kyle Suarez <kyle.suarez@mongodb.com> | 2016-09-06 17:21:48 -0400 |
---|---|---|
committer | Kyle Suarez <kyle.suarez@mongodb.com> | 2016-09-06 17:36:48 -0400 |
commit | 1f389ce467330cda1171d2a04bd0e0b2890aaf8d (patch) | |
tree | 5daf30e4b8cf3a7106eb374b77589e40fb0f6412 /jstests/auth | |
parent | 175408e60358fe0407a4c24e8057a73f0031a9a3 (diff) | |
download | mongo-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.js | 68 | ||||
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 643 | ||||
-rw-r--r-- | jstests/auth/views_authz.js | 2 |
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"; |