summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-03-21 11:22:11 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2017-03-22 13:09:21 -0400
commitd66405f651b0a49a06aacb286e3d1740a0b020af (patch)
tree86f20f45d29d63b53137772c13ea8e917193b18e
parent70151a3b5cc65bd1b16831c523a6f5b477b82c3d (diff)
downloadmongo-d66405f651b0a49a06aacb286e3d1740a0b020af.tar.gz
SERVER-9609 Ensure users can only call getMore on cursors they created
-rw-r--r--jstests/auth/getMore.js238
-rw-r--r--src/mongo/db/auth/authorization_session.cpp22
-rw-r--r--src/mongo/db/auth/authorization_session.h5
-rw-r--r--src/mongo/db/auth/authorization_session_test.cpp156
-rw-r--r--src/mongo/db/clientcursor.cpp1
-rw-r--r--src/mongo/db/clientcursor.h16
-rw-r--r--src/mongo/db/commands/find_cmd.cpp1
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp12
-rw-r--r--src/mongo/db/commands/list_collections.cpp1
-rw-r--r--src/mongo/db/commands/list_indexes.cpp1
-rw-r--r--src/mongo/db/commands/parallel_collection_scan.cpp1
-rw-r--r--src/mongo/db/commands/repair_cursor.cpp1
-rw-r--r--src/mongo/db/commands/run_aggregate.cpp2
-rw-r--r--src/mongo/db/query/find.cpp12
-rw-r--r--src/mongo/dbtests/query_plan_executor.cpp6
-rw-r--r--src/mongo/dbtests/querytests.cpp6
-rw-r--r--src/mongo/s/query/async_results_merger_test.cpp4
-rw-r--r--src/mongo/s/query/cluster_client_cursor.h6
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.cpp5
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.h2
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl_test.cpp8
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.cpp8
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.h2
-rw-r--r--src/mongo/s/query/cluster_client_cursor_params.h25
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp5
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.h6
-rw-r--r--src/mongo/s/query/cluster_find.cpp16
-rw-r--r--src/mongo/s/query/store_possible_cursor.cpp5
28 files changed, 549 insertions, 24 deletions
diff --git a/jstests/auth/getMore.js b/jstests/auth/getMore.js
new file mode 100644
index 00000000000..ba1dc19173b
--- /dev/null
+++ b/jstests/auth/getMore.js
@@ -0,0 +1,238 @@
+// Tests that a user can only run a getMore on a cursor that they created.
+(function() {
+ "use strict";
+
+ function runTest(conn) {
+ let adminDB = conn.getDB("admin");
+ let isMaster = adminDB.runCommand("ismaster");
+ assert.commandWorked(isMaster);
+ const isMongos = (isMaster.msg === "isdbgrid");
+
+ // Create the admin user.
+ assert.commandWorked(
+ adminDB.runCommand({createUser: "admin", pwd: "admin", roles: ["root"]}));
+ assert.eq(1, adminDB.auth("admin", "admin"));
+
+ let ismmap = false;
+ if (!isMongos) {
+ ismmap = assert.commandWorked(adminDB.serverStatus()).storageEngine.name == "mmapv1";
+ }
+
+ // Set up the test database.
+ const testDBName = "auth_getMore";
+ let testDB = adminDB.getSiblingDB(testDBName);
+ testDB.dropDatabase();
+ assert.writeOK(testDB.foo.insert({_id: 0}));
+ assert.writeOK(testDB.foo.insert({_id: 1}));
+ assert.writeOK(testDB.foo.insert({_id: 2}));
+
+ //
+ // Test that a user can only run a getMore on a cursor that they created.
+ //
+
+ // Create two users, "Alice" and "Mallory".
+ assert.commandWorked(
+ testDB.runCommand({createUser: "Alice", pwd: "pwd", roles: ["readWrite"]}));
+ assert.commandWorked(
+ testDB.runCommand({createUser: "Mallory", pwd: "pwd", roles: ["readWrite"]}));
+ adminDB.logout();
+
+ // Test that "Mallory" cannot use a find cursor created by "Alice".
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ let res = assert.commandWorked(testDB.runCommand({find: "foo", batchSize: 0}));
+ let cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's find cursor");
+ testDB.logout();
+
+ // Test that "Mallory" cannot use a legacy find cursor created by "Alice".
+ testDB.getMongo().forceReadMode("legacy");
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ let cursor = testDB.foo.find().batchSize(2);
+ cursor.next();
+ cursor.next();
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.throws(function() {
+ cursor.next();
+ }, [], "read from another user's legacy find cursor");
+ testDB.logout();
+ testDB.getMongo().forceReadMode("commands");
+
+ // Test that "Mallory" cannot use an aggregation cursor created by "Alice".
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(
+ testDB.runCommand({aggregate: "foo", pipeline: [], cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's aggregate cursor");
+ testDB.logout();
+
+ // Test that "Mallory" cannot use a listCollections cursor created by "Alice".
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({listCollections: 1, cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(
+ testDB.runCommand({getMore: cursorId, collection: "$cmd.listCollections"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's listCollections cursor");
+ testDB.logout();
+
+ // Test that "Mallory" cannot use a listIndexes cursor created by "Alice".
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({listIndexes: "foo", cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(
+ testDB.runCommand({getMore: cursorId, collection: "$cmd.listIndexes.foo"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's listIndexes cursor");
+ testDB.logout();
+
+ // Test that "Mallory" cannot use a parallelCollectionScan cursor created by "Alice".
+ if (!isMongos) {
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(
+ testDB.runCommand({parallelCollectionScan: "foo", numCursors: 1}));
+ assert.eq(res.cursors.length, 1, tojson(res));
+ cursorId = res.cursors[0].cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's parallelCollectionScan cursor");
+ testDB.logout();
+ }
+
+ // Test that "Mallory" cannot use a repairCursor cursor created by "Alice".
+ if (!isMongos && ismmap) {
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({repairCursor: "foo"}));
+ cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, testDB.auth("Mallory", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from another user's repairCursor cursor");
+ testDB.logout();
+ }
+
+ //
+ // Test that a user can run a getMore on an aggregate cursor they created, even if some
+ // privileges required for the pipeline have been revoked in the meantime.
+ //
+
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({
+ aggregate: "foo",
+ pipeline: [{$match: {_id: 0}}, {$out: "out"}],
+ cursor: {batchSize: 0}
+ }));
+ cursorId = res.cursor.id;
+ testDB.logout();
+ assert.eq(1, adminDB.auth("admin", "admin"));
+ testDB.revokeRolesFromUser("Alice", ["readWrite"]);
+ testDB.grantRolesToUser("Alice", ["read"]);
+ adminDB.logout();
+ assert.eq(1, testDB.auth("Alice", "pwd"));
+ assert.commandFailedWithCode(
+ testDB.runCommand({aggregate: "foo", pipeline: [{$match: {_id: 0}}, {$out: "out"}]}),
+ ErrorCodes.Unauthorized,
+ "user should no longer have write privileges");
+ res = assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+ assert.eq(1, testDB.out.find().itcount());
+
+ //
+ // Test that if there were multiple users authenticated when the cursor was created, then at
+ // least one of them must be authenticated in order to run getMore on the cursor.
+ //
+
+ assert.eq(1, adminDB.auth("admin", "admin"));
+ assert.writeOK(testDB.bar.insert({_id: 0}));
+
+ // Create a user "fooUser" on the test database that can read the "foo" collection.
+ assert.commandWorked(testDB.runCommand({
+ createRole: "readFoo",
+ privileges: [{resource: {db: testDBName, collection: "foo"}, actions: ["find"]}],
+ roles: []
+ }));
+ assert.commandWorked(
+ testDB.runCommand({createUser: "fooUser", pwd: "pwd", roles: ["readFoo"]}));
+
+ // Create a user "fooBarUser" on the admin database that can read the "foo" and "bar"
+ // collections.
+ assert.commandWorked(adminDB.runCommand({
+ createRole: "readFooBar",
+ privileges: [
+ {resource: {db: testDBName, collection: "foo"}, actions: ["find"]},
+ {resource: {db: testDBName, collection: "bar"}, actions: ["find"]}
+ ],
+ roles: []
+ }));
+ assert.commandWorked(
+ adminDB.runCommand({createUser: "fooBarUser", pwd: "pwd", roles: ["readFooBar"]}));
+
+ adminDB.logout();
+
+ // Test that a cursor created by "fooUser" and "fooBarUser" can be used by "fooUser".
+ assert.eq(1, testDB.auth("fooUser", "pwd"));
+ assert.eq(1, adminDB.auth("fooBarUser", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({find: "foo", batchSize: 0}));
+ cursorId = res.cursor.id;
+ adminDB.logout();
+ assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+ testDB.logout();
+
+ // Test that a cursor created by "fooUser" and "fooBarUser" cannot be used by "fooUser" if
+ // "fooUser" does not have the privilege to read the collection.
+ assert.eq(1, testDB.auth("fooUser", "pwd"));
+ assert.eq(1, adminDB.auth("fooBarUser", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({find: "bar", batchSize: 0}));
+ cursorId = res.cursor.id;
+ adminDB.logout();
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "bar"}),
+ ErrorCodes.Unauthorized,
+ "'fooUser' should not be able to read 'bar' collection");
+ testDB.logout();
+
+ // Test that an aggregate cursor created by "fooUser" and "fooBarUser" can be used by
+ // "fooUser", even if "fooUser" does not have all privileges required by the pipeline. This
+ // is not desirable behavior, but it will be resolved when we require that only one user be
+ // authenticated at a time.
+ assert.eq(1, testDB.auth("fooUser", "pwd"));
+ assert.eq(1, adminDB.auth("fooBarUser", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({
+ aggregate: "foo",
+ pipeline: [
+ {$match: {_id: 0}},
+ {$lookup: {from: "bar", localField: "_id", foreignField: "_id", as: "bar"}}
+ ],
+ cursor: {batchSize: 0}
+ }));
+ cursorId = res.cursor.id;
+ adminDB.logout();
+ res = assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+ assert.eq(res.cursor.nextBatch, [{_id: 0, bar: [{_id: 0}]}], tojson(res));
+ testDB.logout();
+ }
+
+ // Run the test on a standalone.
+ let mongod = MongoRunner.runMongod({auth: "", bind_ip: "127.0.0.1"});
+ runTest(mongod);
+ MongoRunner.stopMongod(mongod);
+
+ // Run the test on a sharded cluster.
+ let cluster = new ShardingTest(
+ {shards: 1, mongos: 1, keyFile: "jstests/libs/key1", other: {shardOptions: {auth: ""}}});
+ runTest(cluster);
+ cluster.stop();
+}());
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 0fdf38884cd..c8fdb8b6332 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -49,6 +49,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/pipeline.h"
+#include "mongo/db/server_options.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
@@ -855,6 +856,27 @@ bool AuthorizationSession::isCoauthorizedWithClient(Client* opClient) {
return false;
}
+bool AuthorizationSession::isCoauthorizedWith(UserNameIterator userNameIter) {
+ if (!getAuthorizationManager().isAuthEnabled()) {
+ return true;
+ }
+ if (!userNameIter.more() && !getAuthenticatedUserNames().more()) {
+ return true;
+ }
+
+ for (; userNameIter.more(); userNameIter.next()) {
+ for (UserNameIterator thisUserNameIter = getAuthenticatedUserNames();
+ thisUserNameIter.more();
+ thisUserNameIter.next()) {
+ if (*userNameIter == *thisUserNameIter) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
UserNameIterator AuthorizationSession::getImpersonatedUserNames() {
return makeUserNameIterator(_impersonatedUserNames.begin(), _impersonatedUserNames.end());
}
diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h
index c2bb3fbfafd..5d6089b3a9f 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -271,6 +271,11 @@ public:
// The existence of 'opClient' must be guaranteed through locks taken by the caller.
bool isCoauthorizedWithClient(Client* opClient);
+ // Returns true if the session and 'userNameIter' share an authenticated user, or if both have
+ // no authenticated users. Impersonated users are not considered as 'authenticated' for the
+ // purpose of this check. This always returns true if auth is not enabled.
+ bool isCoauthorizedWith(UserNameIterator userNameIter);
+
// Tells whether impersonation is active or not. This state is set when
// setImpersonatedUserData is called and cleared when clearImpersonatedUserData is
// called.
diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp
index ec82f193947..3764227069d 100644
--- a/src/mongo/db/auth/authorization_session_test.cpp
+++ b/src/mongo/db/auth/authorization_session_test.cpp
@@ -763,5 +763,161 @@ TEST_F(AuthorizationSessionTest,
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
+
+TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithEmptyUserSet) {
+ std::vector<UserName> userSet;
+ ASSERT_TRUE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsNotCoauthorizedWithNonemptyUserSet) {
+ std::vector<UserName> userSet;
+ userSet.emplace_back("spencer", "test");
+ ASSERT_FALSE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest,
+ UnauthorizedSessionIsCoauthorizedWithNonemptyUserSetWhenAuthIsDisabled) {
+ authzManager->setAuthEnabled(false);
+ std::vector<UserName> userSet;
+ userSet.emplace_back("spencer", "test");
+ ASSERT_TRUE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedWithEmptyUserSet) {
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "spencer"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("spencer", "test")));
+ std::vector<UserName> userSet;
+ ASSERT_FALSE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest,
+ AuthorizedSessionIsCoauthorizedWithEmptyUserSetWhenAuthIsDisabled) {
+ authzManager->setAuthEnabled(false);
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "spencer"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("spencer", "test")));
+ std::vector<UserName> userSet;
+ ASSERT_TRUE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest, AuthorizedSessionIsCoauthorizedWithIntersectingUserSet) {
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "spencer"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "admin"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("spencer", "test")));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("admin", "test")));
+ std::vector<UserName> userSet;
+ userSet.emplace_back("admin", "test");
+ userSet.emplace_back("tess", "test");
+ ASSERT_TRUE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedWithNonintersectingUserSet) {
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "spencer"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "admin"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("spencer", "test")));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("admin", "test")));
+ std::vector<UserName> userSet;
+ userSet.emplace_back("tess", "test");
+ ASSERT_FALSE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
+
+TEST_F(AuthorizationSessionTest,
+ AuthorizedSessionIsCoauthorizedWithNonintersectingUserSetWhenAuthIsDisabled) {
+ authzManager->setAuthEnabled(false);
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "spencer"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(managerState->insertPrivilegeDocument(&_opCtx,
+ BSON("user"
+ << "admin"
+ << "db"
+ << "test"
+ << "credentials"
+ << BSON("MONGODB-CR"
+ << "a")
+ << "roles"
+ << BSONArray()),
+ BSONObj()));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("spencer", "test")));
+ ASSERT_OK(authzSession->addAndAuthorizeUser(&_opCtx, UserName("admin", "test")));
+ std::vector<UserName> userSet;
+ userSet.emplace_back("tess", "test");
+ ASSERT_TRUE(
+ authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end())));
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp
index 70003c2da2b..a662ac9dfbe 100644
--- a/src/mongo/db/clientcursor.cpp
+++ b/src/mongo/db/clientcursor.cpp
@@ -82,6 +82,7 @@ ClientCursor::ClientCursor(ClientCursorParams&& params,
CursorId cursorId)
: _cursorid(cursorId),
_nss(std::move(params.nss)),
+ _authenticatedUsers(std::move(params.authenticatedUsers)),
_isReadCommitted(params.isReadCommitted),
_cursorManager(cursorManager),
_originatingCommand(params.originatingCommandObj),
diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h
index 91e7a0d325c..384cede1fd6 100644
--- a/src/mongo/db/clientcursor.h
+++ b/src/mongo/db/clientcursor.h
@@ -29,6 +29,7 @@
#pragma once
#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/auth/user_name.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/query/plan_executor.h"
@@ -52,6 +53,7 @@ class RecoveryUnit;
struct ClientCursorParams {
ClientCursorParams(std::unique_ptr<PlanExecutor> planExecutor,
NamespaceString nss,
+ UserNameIterator authenticatedUsersIter,
bool isReadCommitted,
BSONObj originatingCommandObj)
: exec(std::move(planExecutor)),
@@ -60,10 +62,15 @@ struct ClientCursorParams {
queryOptions(exec->getCanonicalQuery()
? exec->getCanonicalQuery()->getQueryRequest().getOptions()
: 0),
- originatingCommandObj(originatingCommandObj.getOwned()) {}
+ originatingCommandObj(originatingCommandObj.getOwned()) {
+ while (authenticatedUsersIter.more()) {
+ authenticatedUsers.emplace_back(authenticatedUsersIter.next());
+ }
+ }
std::unique_ptr<PlanExecutor> exec;
const NamespaceString nss;
+ std::vector<UserName> authenticatedUsers;
bool isReadCommitted = false;
int queryOptions = 0;
BSONObj originatingCommandObj;
@@ -97,6 +104,10 @@ public:
return _nss;
}
+ UserNameIterator getAuthenticatedUsers() const {
+ return makeUserNameIterator(_authenticatedUsers.begin(), _authenticatedUsers.end());
+ }
+
bool isReadCommitted() const {
return _isReadCommitted;
}
@@ -253,6 +264,9 @@ private:
// The namespace we're operating on.
const NamespaceString _nss;
+ // The set of authenticated users when this cursor was created.
+ std::vector<UserName> _authenticatedUsers;
+
const bool _isReadCommitted = false;
// A pointer to the CursorManager which owns this cursor. This must be filled out when the
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index 60ae2e2eba4..30620833db1 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -401,6 +401,7 @@ public:
ClientCursorPin pinnedCursor = collection->getCursorManager()->registerCursor(
{std::move(exec),
nss,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
cmdObj});
cursorId = pinnedCursor.getCursor()->cursorid();
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 448177a8068..72e3ec5db34 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -232,6 +232,18 @@ public:
}
}
+ // A user can only call getMore on their own cursor. If there were multiple users
+ // authenticated when the cursor was created, then at least one of them must be
+ // authenticated in order to run getMore on the cursor.
+ if (!AuthorizationSession::get(opCtx->getClient())
+ ->isCoauthorizedWith(cursor->getAuthenticatedUsers())) {
+ return appendCommandStatus(
+ result,
+ Status(ErrorCodes::Unauthorized,
+ str::stream() << "cursor id " << request.cursorid
+ << " was not created by the authenticated user"));
+ }
+
if (request.nss != cursor->nss()) {
return appendCommandStatus(
result,
diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp
index a636ed51d17..a85c0d6abbe 100644
--- a/src/mongo/db/commands/list_collections.cpp
+++ b/src/mongo/db/commands/list_collections.cpp
@@ -330,6 +330,7 @@ public:
auto pinnedCursor = CursorManager::getGlobalCursorManager()->registerCursor(
{std::move(exec),
cursorNss,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
jsobj});
cursorId = pinnedCursor.getCursor()->cursorid();
diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp
index 7ea9b23ed0d..7ef1f599828 100644
--- a/src/mongo/db/commands/list_indexes.cpp
+++ b/src/mongo/db/commands/list_indexes.cpp
@@ -230,6 +230,7 @@ public:
auto pinnedCursor = CursorManager::getGlobalCursorManager()->registerCursor(
{std::move(exec),
cursorNss,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
cmdObj});
cursorId = pinnedCursor.getCursor()->cursorid();
diff --git a/src/mongo/db/commands/parallel_collection_scan.cpp b/src/mongo/db/commands/parallel_collection_scan.cpp
index c46cb6d8c5b..c5166658105 100644
--- a/src/mongo/db/commands/parallel_collection_scan.cpp
+++ b/src/mongo/db/commands/parallel_collection_scan.cpp
@@ -152,6 +152,7 @@ public:
auto pinnedCursor = collection->getCursorManager()->registerCursor(
{std::move(exec),
ns,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
cmdObj});
pinnedCursor.getCursor()->setLeftoverMaxTimeMicros(
diff --git a/src/mongo/db/commands/repair_cursor.cpp b/src/mongo/db/commands/repair_cursor.cpp
index 69ded443ec6..1390b0281b3 100644
--- a/src/mongo/db/commands/repair_cursor.cpp
+++ b/src/mongo/db/commands/repair_cursor.cpp
@@ -109,6 +109,7 @@ public:
auto pinnedCursor = collection->getCursorManager()->registerCursor(
{std::move(exec),
ns,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
cmdObj});
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp
index f0ef162e9af..c7a7e44d913 100644
--- a/src/mongo/db/commands/run_aggregate.cpp
+++ b/src/mongo/db/commands/run_aggregate.cpp
@@ -35,6 +35,7 @@
#include <boost/optional.hpp>
#include <vector>
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/database.h"
#include "mongo/db/curop.h"
#include "mongo/db/db_raii.h"
@@ -418,6 +419,7 @@ Status runAggregate(OperationContext* opCtx,
auto pin = CursorManager::getGlobalCursorManager()->registerCursor(
{std::move(exec),
origNss,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
cmdObj});
ScopeGuard cursorFreer = MakeGuard(&ClientCursorPin::deleteUnderlying, &pin);
diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp
index 62a08389e3f..903153d0824 100644
--- a/src/mongo/db/query/find.cpp
+++ b/src/mongo/db/query/find.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/query/find.h"
#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database_holder.h"
#include "mongo/db/client.h"
@@ -324,6 +325,16 @@ Message getMore(OperationContext* opCtx,
<< " belongs to namespace "
<< cc->nss().ns(),
nss == cc->nss());
+
+ // A user can only call getMore on their own cursor. If there were multiple users
+ // authenticated when the cursor was created, then at least one of them must be
+ // authenticated in order to run getMore on the cursor.
+ uassert(ErrorCodes::Unauthorized,
+ str::stream() << "cursor id " << cursorid
+ << " was not created by the authenticated user",
+ AuthorizationSession::get(opCtx->getClient())
+ ->isCoauthorizedWith(cc->getAuthenticatedUsers()));
+
*isCursorAuthorized = true;
if (cc->isReadCommitted())
@@ -653,6 +664,7 @@ std::string runQuery(OperationContext* opCtx,
ClientCursorPin pinnedCursor = collection->getCursorManager()->registerCursor(
{std::move(exec),
nss,
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
opCtx->recoveryUnit()->isReadingFromMajorityCommittedSnapshot(),
upconvertQueryEntry(q.query, qr.nss(), q.ntoreturn, q.ntoskip)});
ccId = pinnedCursor.getCursor()->cursorid();
diff --git a/src/mongo/dbtests/query_plan_executor.cpp b/src/mongo/dbtests/query_plan_executor.cpp
index b2e15787c0d..3b2ce822a45 100644
--- a/src/mongo/dbtests/query_plan_executor.cpp
+++ b/src/mongo/dbtests/query_plan_executor.cpp
@@ -449,7 +449,7 @@ public:
auto exec = makeCollScanExec(coll, filterObj);
// Make a client cursor from the plan executor.
- coll->getCursorManager()->registerCursor({std::move(exec), nss, false, BSONObj()});
+ coll->getCursorManager()->registerCursor({std::move(exec), nss, {}, false, BSONObj()});
// There should be one cursor before invalidation,
// and zero cursors after invalidation.
@@ -476,7 +476,7 @@ public:
// Make a client cursor from the plan executor.
auto ccPin = collection->getCursorManager()->registerCursor(
- {std::move(exec), nss, false, BSONObj()});
+ {std::move(exec), nss, {}, false, BSONObj()});
// If the cursor is pinned, it sticks around, even after invalidation.
ASSERT_EQUALS(1U, numCursors());
@@ -519,7 +519,7 @@ public:
// Make a client cursor from the plan executor.
collection->getCursorManager()->registerCursor(
- {std::move(exec), nss, false, BSONObj()});
+ {std::move(exec), nss, {}, false, BSONObj()});
}
// There should be one cursor before timeout,
diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp
index d705399e27c..6894117ce4c 100644
--- a/src/mongo/dbtests/querytests.cpp
+++ b/src/mongo/dbtests/querytests.cpp
@@ -1807,7 +1807,7 @@ public:
for (int i = 0; i < 1000; i++) {
auto exec = makeFakePlanExecutor(opCtx.get());
auto cursorPin = CursorManager::getGlobalCursorManager()->registerCursor(
- {std::move(exec), NamespaceString{"test.collection"}, false, BSONObj()});
+ {std::move(exec), NamespaceString{"test.collection"}, {}, false, BSONObj()});
ASSERT_TRUE(CursorManager::isGloballyManagedCursor(cursorPin.getCursor()->cursorid()));
}
}
@@ -1822,7 +1822,7 @@ public:
for (int i = 0; i < 1000; i++) {
auto exec = makeFakePlanExecutor(opCtx.get());
auto cursorPin = testManager.registerCursor(
- {std::move(exec), NamespaceString{"test.collection"}, false, BSONObj()});
+ {std::move(exec), NamespaceString{"test.collection"}, {}, false, BSONObj()});
ASSERT_FALSE(CursorManager::isGloballyManagedCursor(cursorPin.getCursor()->cursorid()));
}
}
@@ -1838,7 +1838,7 @@ public:
for (int i = 0; i < 1000; i++) {
auto exec = makeFakePlanExecutor(opCtx.get());
auto cursorPin = testManager.registerCursor(
- {std::move(exec), NamespaceString{"test.collection"}, false, BSONObj()});
+ {std::move(exec), NamespaceString{"test.collection"}, {}, false, BSONObj()});
auto cursorId = cursorPin.getCursor()->cursorid();
if (prefix) {
ASSERT_EQ(*prefix, extractLeading32Bits(cursorId));
diff --git a/src/mongo/s/query/async_results_merger_test.cpp b/src/mongo/s/query/async_results_merger_test.cpp
index f61cf89f969..0dc06aaa5bd 100644
--- a/src/mongo/s/query/async_results_merger_test.cpp
+++ b/src/mongo/s/query/async_results_merger_test.cpp
@@ -120,7 +120,7 @@ protected:
const auto qr =
unittest::assertGet(QueryRequest::makeFromFindCommand(_nss, findCmd, isExplain));
- _params = stdx::make_unique<ClusterClientCursorParams>(_nss, readPref);
+ _params = stdx::make_unique<ClusterClientCursorParams>(_nss, UserNameIterator(), readPref);
_params->sort = qr->getSort();
_params->limit = qr->getLimit();
_params->batchSize = getMoreBatchSize ? getMoreBatchSize : qr->getBatchSize();
@@ -142,7 +142,7 @@ protected:
*/
void makeCursorFromExistingCursors(
const std::vector<std::pair<HostAndPort, CursorId>>& remotes) {
- _params = stdx::make_unique<ClusterClientCursorParams>(_nss);
+ _params = stdx::make_unique<ClusterClientCursorParams>(_nss, UserNameIterator());
for (const auto& hostIdPair : remotes) {
_params->remotes.emplace_back(hostIdPair.first, hostIdPair.second);
diff --git a/src/mongo/s/query/cluster_client_cursor.h b/src/mongo/s/query/cluster_client_cursor.h
index bd34689e62f..45d5d76a147 100644
--- a/src/mongo/s/query/cluster_client_cursor.h
+++ b/src/mongo/s/query/cluster_client_cursor.h
@@ -30,6 +30,7 @@
#include <boost/optional.hpp>
+#include "mongo/db/auth/user_name.h"
#include "mongo/db/jsobj.h"
#include "mongo/s/query/cluster_query_result.h"
#include "mongo/util/time_support.h"
@@ -80,6 +81,11 @@ public:
virtual bool isTailable() const = 0;
/**
+ * Returns the set of authenticated users when this cursor was created.
+ */
+ virtual UserNameIterator getAuthenticatedUsers() const = 0;
+
+ /**
* Returns the view definition associated with this cursor, if any.
*/
virtual boost::optional<BSONObj> viewDefinition() const = 0;
diff --git a/src/mongo/s/query/cluster_client_cursor_impl.cpp b/src/mongo/s/query/cluster_client_cursor_impl.cpp
index 24ffc0b220a..dec40936d7c 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_impl.cpp
@@ -99,6 +99,11 @@ bool ClusterClientCursorImpl::isTailable() const {
return _params.isTailable;
}
+UserNameIterator ClusterClientCursorImpl::getAuthenticatedUsers() const {
+ return makeUserNameIterator(_params.authenticatedUsers.begin(),
+ _params.authenticatedUsers.end());
+}
+
boost::optional<BSONObj> ClusterClientCursorImpl::viewDefinition() const {
return _params.viewDefinition;
}
diff --git a/src/mongo/s/query/cluster_client_cursor_impl.h b/src/mongo/s/query/cluster_client_cursor_impl.h
index de4e09d0950..27b31dba14c 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.h
+++ b/src/mongo/s/query/cluster_client_cursor_impl.h
@@ -101,6 +101,8 @@ public:
bool isTailable() const final;
+ UserNameIterator getAuthenticatedUsers() const final;
+
boost::optional<BSONObj> viewDefinition() const final;
long long getNumReturnedSoFar() const final;
diff --git a/src/mongo/s/query/cluster_client_cursor_impl_test.cpp b/src/mongo/s/query/cluster_client_cursor_impl_test.cpp
index f8356b9622f..188b8dbf112 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl_test.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_impl_test.cpp
@@ -51,7 +51,7 @@ TEST(ClusterClientCursorImpl, NumReturnedSoFar) {
}
ClusterClientCursorImpl cursor(std::move(mockStage),
- ClusterClientCursorParams(NamespaceString("unused")));
+ ClusterClientCursorParams(NamespaceString("unused"), {}));
ASSERT_EQ(cursor.getNumReturnedSoFar(), 0);
@@ -74,7 +74,7 @@ TEST(ClusterClientCursorImpl, QueueResult) {
mockStage->queueResult(BSON("a" << 4));
ClusterClientCursorImpl cursor(std::move(mockStage),
- ClusterClientCursorParams(NamespaceString("unused")));
+ ClusterClientCursorParams(NamespaceString("unused"), {}));
auto firstResult = cursor.next(nullptr);
ASSERT_OK(firstResult.getStatus());
@@ -113,7 +113,7 @@ TEST(ClusterClientCursorImpl, RemotesExhausted) {
mockStage->markRemotesExhausted();
ClusterClientCursorImpl cursor(std::move(mockStage),
- ClusterClientCursorParams(NamespaceString("unused")));
+ ClusterClientCursorParams(NamespaceString("unused"), {}));
ASSERT_TRUE(cursor.remotesExhausted());
auto firstResult = cursor.next(nullptr);
@@ -142,7 +142,7 @@ TEST(ClusterClientCursorImpl, ForwardsAwaitDataTimeout) {
ASSERT_NOT_OK(mockStage->getAwaitDataTimeout().getStatus());
ClusterClientCursorImpl cursor(std::move(mockStage),
- ClusterClientCursorParams(NamespaceString("unused")));
+ ClusterClientCursorParams(NamespaceString("unused"), {}));
ASSERT_OK(cursor.setAwaitDataTimeout(Milliseconds(789)));
auto awaitDataTimeout = mockStagePtr->getAwaitDataTimeout();
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.cpp b/src/mongo/s/query/cluster_client_cursor_mock.cpp
index 28a4f2643f3..8b4d64f4d41 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_mock.cpp
@@ -77,6 +77,14 @@ bool ClusterClientCursorMock::isTailable() const {
return false;
}
+namespace {
+const std::vector<UserName> emptyAuthenticatedUsers{};
+} // namespace
+
+UserNameIterator ClusterClientCursorMock::getAuthenticatedUsers() const {
+ return makeUserNameIterator(emptyAuthenticatedUsers.begin(), emptyAuthenticatedUsers.end());
+}
+
boost::optional<BSONObj> ClusterClientCursorMock::viewDefinition() const {
return boost::none;
}
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.h b/src/mongo/s/query/cluster_client_cursor_mock.h
index baea6660535..23d857d59d6 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.h
+++ b/src/mongo/s/query/cluster_client_cursor_mock.h
@@ -49,6 +49,8 @@ public:
bool isTailable() const final;
+ UserNameIterator getAuthenticatedUsers() const final;
+
boost::optional<BSONObj> viewDefinition() const final;
long long getNumReturnedSoFar() const final;
diff --git a/src/mongo/s/query/cluster_client_cursor_params.h b/src/mongo/s/query/cluster_client_cursor_params.h
index bf7037f98e5..03db1e37194 100644
--- a/src/mongo/s/query/cluster_client_cursor_params.h
+++ b/src/mongo/s/query/cluster_client_cursor_params.h
@@ -34,6 +34,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/client/read_preference.h"
+#include "mongo/db/auth/user_name.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/namespace_string.h"
#include "mongo/s/client/shard.h"
@@ -91,21 +92,27 @@ struct ClusterClientCursorParams {
};
/**
- * Constructor used for cases where initial shard host targeting is necessary (i.e., we don't
+ * Read preference must be provided if initial shard host targeting is necessary (i.e., we don't
* know yet the remote cursor id).
*/
- ClusterClientCursorParams(NamespaceString nss, ReadPreferenceSetting readPref)
- : nsString(std::move(nss)), readPreference(std::move(readPref)) {}
-
- /**
- * Constructor used for cases, where the remote cursor ids are already known and no resolution
- * or retargeting needs to happen.
- */
- ClusterClientCursorParams(NamespaceString nss) : nsString(std::move(nss)) {}
+ ClusterClientCursorParams(NamespaceString nss,
+ UserNameIterator authenticatedUsersIter,
+ boost::optional<ReadPreferenceSetting> readPref = boost::none)
+ : nsString(std::move(nss)) {
+ while (authenticatedUsersIter.more()) {
+ authenticatedUsers.emplace_back(authenticatedUsersIter.next());
+ }
+ if (readPref) {
+ readPreference = std::move(readPref.get());
+ }
+ }
// Namespace against which to query.
NamespaceString nsString;
+ // The set of authenticated users when this cursor was created.
+ std::vector<UserName> authenticatedUsers;
+
// Per-remote node data.
std::vector<Remote> remotes;
diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp
index 85d396490c6..abb5513cdd8 100644
--- a/src/mongo/s/query/cluster_cursor_manager.cpp
+++ b/src/mongo/s/query/cluster_cursor_manager.cpp
@@ -120,6 +120,11 @@ bool ClusterCursorManager::PinnedCursor::isTailable() const {
return _cursor->isTailable();
}
+UserNameIterator ClusterCursorManager::PinnedCursor::getAuthenticatedUsers() const {
+ invariant(_cursor);
+ return _cursor->getAuthenticatedUsers();
+}
+
void ClusterCursorManager::PinnedCursor::returnCursor(CursorState cursorState) {
invariant(_cursor);
// Note that unpinning a cursor transfers ownership of the underlying ClusterClientCursor object
diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h
index ad320452b3b..d71c6966ee7 100644
--- a/src/mongo/s/query/cluster_cursor_manager.h
+++ b/src/mongo/s/query/cluster_cursor_manager.h
@@ -163,6 +163,12 @@ public:
bool isTailable() const;
/**
+ * Returns the set of authenticated users when this cursor was created. Cannot be called
+ * after returnCursor() is called. A cursor must be owned.
+ */
+ UserNameIterator getAuthenticatedUsers() const;
+
+ /**
* Transfers ownership of the underlying cursor back to the manager. A cursor must be
* owned, and a cursor will no longer be owned after this method completes.
*
diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp
index 3d4c384c506..a6dcf1de83e 100644
--- a/src/mongo/s/query/cluster_find.cpp
+++ b/src/mongo/s/query/cluster_find.cpp
@@ -39,6 +39,7 @@
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/connpool.h"
#include "mongo/client/read_preference.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
#include "mongo/db/query/canonical_query.h"
#include "mongo/db/query/find_common.h"
@@ -179,7 +180,10 @@ StatusWith<CursorId> runQueryWithoutRetrying(OperationContext* opCtx,
}
}
- ClusterClientCursorParams params(query.nss(), readPref);
+ ClusterClientCursorParams params(
+ query.nss(),
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
+ readPref);
params.limit = query.getQueryRequest().getLimit();
params.batchSize = query.getQueryRequest().getEffectiveBatchSize();
params.skip = query.getQueryRequest().getSkip();
@@ -381,6 +385,16 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx,
while (MONGO_FAIL_POINT(keepCursorPinnedDuringGetMore)) {
}
+ // A user can only call getMore on their own cursor. If there were multiple users authenticated
+ // when the cursor was created, then at least one of them must be authenticated in order to run
+ // getMore on the cursor.
+ if (!AuthorizationSession::get(opCtx->getClient())
+ ->isCoauthorizedWith(pinnedCursor.getValue().getAuthenticatedUsers())) {
+ return {ErrorCodes::Unauthorized,
+ str::stream() << "cursor id " << request.cursorid
+ << " was not created by the authenticated user"};
+ }
+
if (request.awaitDataTimeout) {
auto status = pinnedCursor.getValue().setAwaitDataTimeout(*request.awaitDataTimeout);
if (!status.isOK()) {
diff --git a/src/mongo/s/query/store_possible_cursor.cpp b/src/mongo/s/query/store_possible_cursor.cpp
index 8647871b6a7..4f53b2441bc 100644
--- a/src/mongo/s/query/store_possible_cursor.cpp
+++ b/src/mongo/s/query/store_possible_cursor.cpp
@@ -32,6 +32,7 @@
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/query/cursor_response.h"
#include "mongo/s/query/cluster_client_cursor_impl.h"
#include "mongo/s/query/cluster_client_cursor_params.h"
@@ -58,7 +59,9 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
return cmdResult;
}
- ClusterClientCursorParams params(incomingCursorResponse.getValue().getNSS());
+ ClusterClientCursorParams params(
+ incomingCursorResponse.getValue().getNSS(),
+ AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames());
params.remotes.emplace_back(server, incomingCursorResponse.getValue().getCursorId());