summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2017-03-10 13:19:51 -0500
committerTess Avitabile <tess.avitabile@mongodb.com>2017-03-17 10:09:58 -0400
commit9e7974e4b6e2b3fe5e7741dce6549624113af196 (patch)
treee5d9840faefc88ae5ba3fb81e2e481fe1bc5cd39 /src/mongo
parent5df5125fd63295a9b71d79e68a84ba51e0c1c87f (diff)
downloadmongo-9e7974e4b6e2b3fe5e7741dce6549624113af196.tar.gz
SERVER-9609 Ensure users can only call getMore on cursors they created
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/auth/authorization_session.cpp18
-rw-r--r--src/mongo/db/auth/authorization_session.h5
-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
26 files changed, 151 insertions, 24 deletions
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 0fdf38884cd..27cbc3a79a5 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -855,6 +855,24 @@ bool AuthorizationSession::isCoauthorizedWithClient(Client* opClient) {
return false;
}
+bool AuthorizationSession::isCoauthorizedWith(UserNameIterator userNameIter) {
+ if (!userNameIter.more() && !getAuthenticatedUserNames().more()) {
+ return true;
+ }
+
+ while (userNameIter.more()) {
+ for (UserNameIterator thisUserNameIter = getAuthenticatedUserNames();
+ thisUserNameIter.more();
+ thisUserNameIter.next()) {
+ if (userNameIter.next() == *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..454b8c751dc 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.
+ 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/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());