diff options
author | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-03-10 13:19:51 -0500 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-03-17 10:09:58 -0400 |
commit | 9e7974e4b6e2b3fe5e7741dce6549624113af196 (patch) | |
tree | e5d9840faefc88ae5ba3fb81e2e481fe1bc5cd39 /src/mongo | |
parent | 5df5125fd63295a9b71d79e68a84ba51e0c1c87f (diff) | |
download | mongo-9e7974e4b6e2b3fe5e7741dce6549624113af196.tar.gz |
SERVER-9609 Ensure users can only call getMore on cursors they created
Diffstat (limited to 'src/mongo')
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()); |