summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsabella Siu <isabella.siu@10gen.com>2019-01-11 11:16:24 -0500
committerIsabella Siu <isabella.siu@10gen.com>2019-02-08 14:34:32 -0500
commit8e5e745e98d33633e7d24a2629f22cdba79d9851 (patch)
tree9e02d92dadcf67140fe8707d792e55cf12b5443e
parent7a7baa2539ec169335086e45c7d0b85ba7cdb877 (diff)
downloadmongo-8e5e745e98d33633e7d24a2629f22cdba79d9851.tar.gz
SERVER-37836 re-evaluate authorization for originating command in getMore
-rw-r--r--jstests/auth/getMore.js133
-rw-r--r--src/mongo/db/auth/authorization_session.h21
-rw-r--r--src/mongo/db/auth/authorization_session_impl.cpp87
-rw-r--r--src/mongo/db/auth/authorization_session_impl.h11
-rw-r--r--src/mongo/db/auth/authorization_session_test.cpp205
-rw-r--r--src/mongo/db/clientcursor.cpp1
-rw-r--r--src/mongo/db/clientcursor.h23
-rw-r--r--src/mongo/db/commands/count_cmd.cpp3
-rw-r--r--src/mongo/db/commands/current_op.cpp13
-rw-r--r--src/mongo/db/commands/distinct.cpp10
-rw-r--r--src/mongo/db/commands/find_cmd.cpp9
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp11
-rw-r--r--src/mongo/db/commands/list_collections.cpp6
-rw-r--r--src/mongo/db/commands/list_indexes.cpp3
-rw-r--r--src/mongo/db/commands/pipeline_command.cpp26
-rw-r--r--src/mongo/db/commands/repair_cursor.cpp3
-rw-r--r--src/mongo/db/commands/run_aggregate.cpp7
-rw-r--r--src/mongo/db/commands/run_aggregate.h5
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp2
-rw-r--r--src/mongo/db/query/find.cpp3
-rw-r--r--src/mongo/dbtests/cursor_manager_test.cpp33
-rw-r--r--src/mongo/embedded/embedded_auth_session.cpp15
-rw-r--r--src/mongo/s/commands/cluster_count_cmd.cpp3
-rw-r--r--src/mongo/s/commands/cluster_current_op.cpp6
-rw-r--r--src/mongo/s/commands/cluster_distinct_cmd.cpp3
-rw-r--r--src/mongo/s/commands/cluster_find_cmd.cpp17
-rw-r--r--src/mongo/s/commands/cluster_pipeline_cmd.cpp49
-rw-r--r--src/mongo/s/commands/commands_public.cpp27
-rw-r--r--src/mongo/s/query/cluster_aggregate.cpp58
-rw-r--r--src/mongo/s/query/cluster_aggregate.h9
-rw-r--r--src/mongo/s/query/cluster_client_cursor.h7
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.cpp4
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.h3
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.cpp4
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.h6
-rw-r--r--src/mongo/s/query/cluster_client_cursor_params.h4
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp5
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.h7
-rw-r--r--src/mongo/s/query/cluster_find.cpp10
-rw-r--r--src/mongo/s/query/store_possible_cursor.cpp6
-rw-r--r--src/mongo/s/query/store_possible_cursor.h12
-rw-r--r--src/mongo/s/query/store_possible_cursor_test.cpp9
42 files changed, 631 insertions, 248 deletions
diff --git a/jstests/auth/getMore.js b/jstests/auth/getMore.js
index 49c60fcf6ca..d58c52a205c 100644
--- a/jstests/auth/getMore.js
+++ b/jstests/auth/getMore.js
@@ -40,6 +40,7 @@
assert.eq(1, testDB.auth("Alice", "pwd"));
let res = assert.commandWorked(testDB.runCommand({find: "foo", batchSize: 0}));
let cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
testDB.logout();
assert.eq(1, testDB.auth("Mallory", "pwd"));
assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
@@ -66,6 +67,7 @@
res = assert.commandWorked(
testDB.runCommand({aggregate: "foo", pipeline: [], cursor: {batchSize: 0}}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
testDB.logout();
assert.eq(1, testDB.auth("Mallory", "pwd"));
assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
@@ -77,6 +79,7 @@
assert.eq(1, testDB.auth("Alice", "pwd"));
res = assert.commandWorked(testDB.runCommand({listCollections: 1, cursor: {batchSize: 0}}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
testDB.logout();
assert.eq(1, testDB.auth("Mallory", "pwd"));
assert.commandFailedWithCode(
@@ -89,6 +92,7 @@
assert.eq(1, testDB.auth("Alice", "pwd"));
res = assert.commandWorked(testDB.runCommand({listIndexes: "foo", cursor: {batchSize: 0}}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
testDB.logout();
assert.eq(1, testDB.auth("Mallory", "pwd"));
assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
@@ -97,7 +101,7 @@
testDB.logout();
//
- // Test that a user can call getMore on an indexStats cursor they created, even if the
+ // Test that a user can call getMore on an indexStats cursor they created, unless the
// indexStats privilege has been revoked in the meantime.
//
@@ -115,6 +119,13 @@
res = assert.commandWorked(testDB.runCommand(
{aggregate: "foo", pipeline: [{$indexStats: {}}], cursor: {batchSize: 0}}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+
+ res = assert.commandWorked(testDB.runCommand(
+ {aggregate: "foo", pipeline: [{$indexStats: {}}], cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
testDB.logout();
assert.eq(1, adminDB.auth("admin", "admin"));
@@ -123,11 +134,79 @@
adminDB.logout();
assert.eq(1, testDB.auth("Bob", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from a cursor without required privileges");
+ testDB.logout();
+
+ //
+ // Test that a user can call getMore on a listCollections cursor they created, unless the
+ // readWrite privilege has been revoked in the meantime.
+ //
+
+ assert.eq(1, adminDB.auth("admin", "admin"));
+
+ assert.commandWorked(
+ testDB.runCommand({createUser: "Tom", pwd: "pwd", roles: ["readWrite"]}));
+ adminDB.logout();
+
+ assert.eq(1, testDB.auth("Tom", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({listCollections: 1, cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ assert.commandWorked(
+ testDB.runCommand({getMore: cursorId, collection: "$cmd.listCollections"}));
+
+ res = assert.commandWorked(testDB.runCommand({listCollections: 1, cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ testDB.logout();
+
+ assert.eq(1, adminDB.auth("admin", "admin"));
+ assert.commandWorked(testDB.runCommand({revokeRolesFromUser: "Tom", roles: ["readWrite"]}));
+ adminDB.logout();
+
+ assert.eq(1, testDB.auth("Tom", "pwd"));
+ assert.commandFailedWithCode(
+ testDB.runCommand({getMore: cursorId, collection: "$cmd.listCollections"}),
+ ErrorCodes.Unauthorized,
+ "read from a cursor without required privileges");
+ testDB.logout();
+ //
+ // Test that a user can call getMore on a listIndexes cursor they created, unless the
+ // readWrite privilege has been revoked in the meantime.
+ //
+
+ assert.eq(1, adminDB.auth("admin", "admin"));
+
+ assert.commandWorked(
+ testDB.runCommand({createUser: "Bill", pwd: "pwd", roles: ["readWrite"]}));
+ adminDB.logout();
+
+ assert.eq(1, testDB.auth("Bill", "pwd"));
+ res = assert.commandWorked(testDB.runCommand({listIndexes: "foo", cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+
+ res = assert.commandWorked(testDB.runCommand({listIndexes: "foo", cursor: {batchSize: 0}}));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ testDB.logout();
+
+ assert.eq(1, adminDB.auth("admin", "admin"));
+ assert.commandWorked(
+ testDB.runCommand({revokeRolesFromUser: "Bill", roles: ["readWrite"]}));
+ adminDB.logout();
+
+ assert.eq(1, testDB.auth("Bill", "pwd"));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from a cursor without required privileges");
testDB.logout();
//
- // Test that a user can run a getMore on an aggregate cursor they created, even if some
+ // Test that a user can run a getMore on an aggregate cursor they created, unless some
// privileges required for the pipeline have been revoked in the meantime.
//
@@ -138,19 +217,33 @@
cursor: {batchSize: 0}
}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+
+ res = assert.commandWorked(testDB.runCommand({
+ aggregate: "foo",
+ pipeline: [{$match: {_id: 0}}, {$out: "out"}],
+ cursor: {batchSize: 0}
+ }));
+ cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
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"}], cursor: {}}),
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());
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "wrote from a cursor without required privileges");
+ testDB.logout();
//
// Test that if there were multiple users authenticated when the cursor was created, then at
@@ -189,24 +282,26 @@
assert.eq(1, adminDB.auth("fooBarUser", "pwd"));
res = assert.commandWorked(testDB.runCommand({find: "foo", batchSize: 0}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
adminDB.logout();
assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
testDB.logout();
- // Test that a cursor created by "fooUser" and "fooBarUser" can be used by "fooUser" even if
+ // 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;
+ assert.neq(0, cursorId);
adminDB.logout();
- assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "bar"}));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "bar"}),
+ ErrorCodes.Unauthorized,
+ "read from a cursor without required privileges");
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.
+ // Test that an aggregate cursor created by "fooUser" and "fooBarUser" cannot be used by
+ // "fooUser" if "fooUser" does not have all privileges required by the pipeline.
assert.eq(1, testDB.auth("fooUser", "pwd"));
assert.eq(1, adminDB.auth("fooBarUser", "pwd"));
res = assert.commandWorked(testDB.runCommand({
@@ -218,9 +313,23 @@
cursor: {batchSize: 0}
}));
cursorId = res.cursor.id;
+ assert.neq(0, cursorId);
+ assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
+
+ 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;
+ assert.neq(0, cursorId);
adminDB.logout();
- res = assert.commandWorked(testDB.runCommand({getMore: cursorId, collection: "foo"}));
- assert.eq(res.cursor.nextBatch, [{_id: 0, bar: [{_id: 0}]}], tojson(res));
+ assert.commandFailedWithCode(testDB.runCommand({getMore: cursorId, collection: "foo"}),
+ ErrorCodes.Unauthorized,
+ "read from a cursor without required privileges");
testDB.logout();
}
diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h
index e4ce0845695..25e40608eb7 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -219,11 +219,12 @@ public:
virtual Status checkAuthForKillCursors(const NamespaceString& cursorNss,
UserNameIterator cursorOwner) = 0;
- // Checks if this connection has the privileges necessary to run the aggregation pipeline
- // specified in 'cmdObj' on the namespace 'ns' either directly on mongoD or via mongoS.
- virtual Status checkAuthForAggregate(const NamespaceString& ns,
- const BSONObj& cmdObj,
- bool isMongos) = 0;
+ // Attempts to get the privileges necessary to run the aggregation pipeline specified in
+ // 'cmdObj' on the namespace 'ns' either directly on mongoD or via mongoS. Returns a non-ok
+ // status if it is unable to parse the pipeline.
+ virtual StatusWith<PrivilegeVector> getPrivilegesForAggregate(const NamespaceString& ns,
+ const BSONObj& cmdObj,
+ bool isMongos) = 0;
// Checks if this connection has the privileges necessary to create 'ns' with the options
// supplied in 'cmdObj' either directly on mongoD or via mongoS.
@@ -245,6 +246,12 @@ public:
// from a role.
virtual Status checkAuthorizedToRevokePrivilege(const Privilege& privilege) = 0;
+ // Checks if the current session is authorized to list the collections in the given
+ // database. If it is, return a privilegeVector containing the privileges used to authorize
+ // this command.
+ virtual StatusWith<PrivilegeVector> checkAuthorizedToListCollections(StringData dbname,
+ const BSONObj& cmdObj) = 0;
+
// Checks if this connection is using the localhost bypass
virtual bool isUsingLocalhostBypass() = 0;
@@ -271,10 +278,6 @@ public:
// is allowed to change his/her own password
virtual bool isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) = 0;
- // Returns true if the current session is authorized to list the collections in the given
- // database.
- virtual bool isAuthorizedToListCollections(StringData dbname, const BSONObj& cmdObj) = 0;
-
// Returns true if the current session is authenticated as the given user and that user
// is allowed to change his/her own customData.
virtual bool isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) = 0;
diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp
index c54b632d943..72f879f7344 100644
--- a/src/mongo/db/auth/authorization_session_impl.cpp
+++ b/src/mongo/db/auth/authorization_session_impl.cpp
@@ -87,10 +87,15 @@ Status checkAuthForCreateOrModifyView(AuthorizationSession* authzSession,
// This check performs some validation but it is not exhaustive and may allow for an invalid
// pipeline specification. In this case the authorization check will succeed but the pipeline
// will fail to parse later in Command::run().
- return authzSession->checkAuthForAggregate(
+ auto statusWithPrivs = authzSession->getPrivilegesForAggregate(
viewOnNs,
BSON("aggregate" << viewOnNs.coll() << "pipeline" << viewPipeline << "cursor" << BSONObj()),
isMongos);
+ PrivilegeVector privileges = uassertStatusOK(statusWithPrivs);
+ if (!authzSession->isAuthorizedForPrivileges(privileges)) {
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+ return Status::OK();
}
} // namespace
@@ -236,23 +241,19 @@ PrivilegeVector AuthorizationSessionImpl::getDefaultPrivileges() {
return defaultPrivileges;
}
-Status AuthorizationSessionImpl::checkAuthForAggregate(const NamespaceString& nss,
- const BSONObj& cmdObj,
- bool isMongos) {
+StatusWith<PrivilegeVector> AuthorizationSessionImpl::getPrivilegesForAggregate(
+ const NamespaceString& nss, const BSONObj& cmdObj, bool isMongos) {
if (!nss.isValid()) {
return Status(ErrorCodes::InvalidNamespace,
mongoutils::str::stream() << "Invalid input namespace, " << nss.ns());
}
+ PrivilegeVector privileges;
+
// If this connection does not need to be authenticated (for instance, if auth is disabled),
- // return Status::OK() immediately.
+ // returns an empty requirements set.
if (_externalState->shouldIgnoreAuthChecks()) {
- return Status::OK();
- }
-
- // We require at least one authenticated user when running aggregate with auth enabled.
- if (!isAuthenticated()) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ return privileges;
}
auto statusWithAggRequest = AggregationRequest::parseFromBSON(nss, cmdObj);
@@ -265,44 +266,28 @@ Status AuthorizationSessionImpl::checkAuthForAggregate(const NamespaceString& ns
// If the aggregation pipeline is empty, confirm the user is authorized for find on 'nss'.
if (pipeline.empty()) {
- if (!isAuthorizedForPrivilege(
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find))) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
-
- return Status::OK();
+ Privilege currentPriv =
+ Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find);
+ Privilege::addPrivilegeToPrivilegeVector(&privileges, currentPriv);
+ return privileges;
}
- // Confirm the user is authorized for the pipeline's initial document source. We confirm a user
- // is authorized incrementally rather than once for the entire pipeline. This will prevent a
- // malicious user, who doesn't have access to the initial document source, from consuming the
- // resources needed to parse a potentially large pipeline.
- auto liteParsedFirstDocumentSource = LiteParsedDocumentSource::parse(aggRequest, pipeline[0]);
- if (!liteParsedFirstDocumentSource->isInitialSource() &&
- !isAuthorizedForPrivilege(
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find))) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
-
- // We have done the work to lite parse the first stage. Given that, we check required privileges
- // for it using 'liteParsedFirstDocumentSource' regardless of whether is an initial source or
- // not.
- if (!isAuthorizedForPrivileges(liteParsedFirstDocumentSource->requiredPrivileges(isMongos))) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ // If the first stage of the pipeline is not an initial source, the pipeline is implicitly
+ // reading documents from the underlying collection. The client must be authorized to do so.
+ auto liteParsedDocSource = LiteParsedDocumentSource::parse(aggRequest, pipeline[0]);
+ if (!liteParsedDocSource->isInitialSource()) {
+ Privilege currentPriv =
+ Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find);
+ Privilege::addPrivilegeToPrivilegeVector(&privileges, currentPriv);
}
- // Confirm privileges for the remainder of the pipepline. Start with the second stage as we have
- // already authorized the first.
- auto pipelineIter = pipeline.begin() + 1;
-
- for (; pipelineIter != pipeline.end(); ++pipelineIter) {
- auto liteParsedDocSource = LiteParsedDocumentSource::parse(aggRequest, *pipelineIter);
- if (!isAuthorizedForPrivileges(liteParsedDocSource->requiredPrivileges(isMongos))) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
+ // Confirm privileges for the pipeline.
+ for (auto&& pipelineStage : pipeline) {
+ liteParsedDocSource = LiteParsedDocumentSource::parse(aggRequest, pipelineStage);
+ PrivilegeVector currentPrivs = liteParsedDocSource->requiredPrivileges(isMongos);
+ Privilege::addPrivilegesToPrivilegeVector(&privileges, currentPrivs);
}
-
- return Status::OK();
+ return privileges;
}
Status AuthorizationSessionImpl::checkAuthForFind(const NamespaceString& ns, bool hasTerm) {
@@ -699,16 +684,20 @@ bool AuthorizationSessionImpl::isAuthorizedToChangeOwnCustomDataAsUser(const Use
ActionType::changeOwnCustomData);
}
-bool AuthorizationSessionImpl::isAuthorizedToListCollections(StringData dbname,
- const BSONObj& cmdObj) {
+StatusWith<PrivilegeVector> AuthorizationSessionImpl::checkAuthorizedToListCollections(
+ StringData dbname, const BSONObj& cmdObj) {
if (cmdObj["authorizedCollections"].trueValue() && cmdObj["nameOnly"].trueValue() &&
AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(dbname)) {
- return true;
+ return PrivilegeVector();
}
// Check for the listCollections ActionType on the database.
- return AuthorizationSessionImpl::isAuthorizedForActionsOnResource(
- ResourcePattern::forDatabaseName(dbname), ActionType::listCollections);
+ PrivilegeVector privileges = {
+ Privilege(ResourcePattern::forDatabaseName(dbname), ActionType::listCollections)};
+ if (AuthorizationSessionImpl::isAuthorizedForPrivileges(privileges)) {
+ return privileges;
+ }
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
}
bool AuthorizationSessionImpl::isAuthenticatedAsUserWithRole(const RoleName& roleName) {
diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h
index f8ca7c286db..0b432c3fe7d 100644
--- a/src/mongo/db/auth/authorization_session_impl.h
+++ b/src/mongo/db/auth/authorization_session_impl.h
@@ -122,9 +122,9 @@ public:
Status checkAuthForKillCursors(const NamespaceString& cursorNss,
UserNameIterator cursorOwner) override;
- Status checkAuthForAggregate(const NamespaceString& ns,
- const BSONObj& cmdObj,
- bool isMongos) override;
+ StatusWith<PrivilegeVector> getPrivilegesForAggregate(const NamespaceString& ns,
+ const BSONObj& cmdObj,
+ bool isMongos) override;
Status checkAuthForCreate(const NamespaceString& ns,
const BSONObj& cmdObj,
@@ -134,6 +134,9 @@ public:
const BSONObj& cmdObj,
bool isMongos) override;
+ StatusWith<PrivilegeVector> checkAuthorizedToListCollections(StringData dbname,
+ const BSONObj& cmdObj) override;
+
Status checkAuthorizedToGrantPrivilege(const Privilege& privilege) override;
Status checkAuthorizedToRevokePrivilege(const Privilege& privilege) override;
@@ -152,8 +155,6 @@ public:
bool isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) override;
- bool isAuthorizedToListCollections(StringData dbname, const BSONObj& cmdObj) override;
-
bool isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) override;
bool isAuthenticatedAsUserWithRole(const RoleName& roleName) override;
diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp
index e842e27a06c..b492704232a 100644
--- a/src/mongo/db/auth/authorization_session_test.cpp
+++ b/src/mongo/db/auth/authorization_session_test.cpp
@@ -640,16 +640,19 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineIsNotAnArra
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONObj cmdObjIntPipeline = BSON("aggregate" << testFooNss.coll() << "pipeline" << 7);
- ASSERT_EQ(ErrorCodes::TypeMismatch,
- authzSession->checkAuthForAggregate(testFooNss, cmdObjIntPipeline, false));
+ ASSERT_EQ(
+ ErrorCodes::TypeMismatch,
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdObjIntPipeline, false).getStatus());
BSONObj cmdObjObjPipeline = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONObj());
- ASSERT_EQ(ErrorCodes::TypeMismatch,
- authzSession->checkAuthForAggregate(testFooNss, cmdObjObjPipeline, false));
+ ASSERT_EQ(
+ ErrorCodes::TypeMismatch,
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdObjObjPipeline, false).getStatus());
BSONObj cmdObjNoPipeline = BSON("aggregate" << testFooNss.coll());
- ASSERT_EQ(ErrorCodes::TypeMismatch,
- authzSession->checkAuthForAggregate(testFooNss, cmdObjNoPipeline, false));
+ ASSERT_EQ(
+ ErrorCodes::TypeMismatch,
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdObjNoPipeline, false).getStatus());
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineFirstStageIsNotAnObject) {
@@ -658,19 +661,22 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineFirstStageI
BSONObj cmdObjFirstStageInt =
BSON("aggregate" << testFooNss.coll() << "pipeline" << BSON_ARRAY(7));
ASSERT_EQ(ErrorCodes::TypeMismatch,
- authzSession->checkAuthForAggregate(testFooNss, cmdObjFirstStageInt, false));
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdObjFirstStageInt, false)
+ .getStatus());
BSONObj cmdObjFirstStageArray =
BSON("aggregate" << testFooNss.coll() << "pipeline" << BSON_ARRAY(BSONArray()));
ASSERT_EQ(ErrorCodes::TypeMismatch,
- authzSession->checkAuthForAggregate(testFooNss, cmdObjFirstStageArray, false));
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdObjFirstStageArray, false)
+ .getStatus());
}
TEST_F(AuthorizationSessionTest, CannotAggregateEmptyPipelineWithoutFindAction) {
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray() << "cursor"
<< BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateEmptyPipelineWithFindAction) {
@@ -678,7 +684,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateEmptyPipelineWithFindAction) {
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray() << "cursor"
<< BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateWithoutFindActionIfFirstStageNotIndexOrCollStats) {
@@ -689,8 +697,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateWithoutFindActionIfFirstStageNot
<< BSON("$indexStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateWithFindActionIfPipelineContainsIndexOrCollStats) {
@@ -699,8 +708,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateWithFindActionIfPipelineContains
<< BSON("$indexStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction) {
@@ -709,8 +719,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction)
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) {
@@ -719,7 +730,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) {
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsAction) {
@@ -728,8 +741,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsActio
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) {
@@ -738,7 +752,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) {
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersFalseWithoutInprogActionOnMongoD) {
@@ -747,7 +763,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersFalseWithoutInprog
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseWithoutInprogActionOnMongoS) {
@@ -756,17 +774,16 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseWithoutInp
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthenticatedOnMongoD) {
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
-
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthenticated());
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthenticatedOnMongoS) {
@@ -774,8 +791,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthe
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInprogActionOnMongoD) {
@@ -784,8 +802,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInprogActionOnMongoS) {
@@ -794,8 +813,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActionOnMongoD) {
@@ -805,7 +825,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActionOnMongoS) {
@@ -815,7 +837,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMongoD) {
@@ -825,8 +849,9 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon
BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMongoS) {
@@ -836,8 +861,9 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon
BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotValid) {
@@ -847,7 +873,7 @@ TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotVa
<< ""));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_THROWS_CODE(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false),
+ ASSERT_THROWS_CODE(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false),
AssertionException,
ErrorCodes::InvalidNamespace);
}
@@ -859,20 +885,19 @@ TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTarge
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
// We have insert but not remove on the $out namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::insert})});
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
// We have remove but not insert on the $out namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::remove})});
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNamespace) {
@@ -883,15 +908,18 @@ TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNames
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
BSONObj cmdObjNoBypassDocumentValidation = BSON(
"aggregate" << testFooNss.coll() << "pipeline" << pipeline << "bypassDocumentValidation"
<< false
<< "cursor"
<< BSONObj());
- ASSERT_OK(
- authzSession->checkAuthForAggregate(testFooNss, cmdObjNoBypassDocumentValidation, false));
+ privileges = uassertStatusOK(authzSession->getPrivilegesForAggregate(
+ testFooNss, cmdObjNoBypassDocumentValidation, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest,
@@ -905,8 +933,9 @@ TEST_F(AuthorizationSessionTest,
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj()
<< "bypassDocumentValidation"
<< true);
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest,
@@ -922,7 +951,9 @@ TEST_F(AuthorizationSessionTest,
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj()
<< "bypassDocumentValidation"
<< true);
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespace) {
@@ -931,8 +962,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespa
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) {
@@ -942,7 +974,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) {
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
@@ -955,8 +989,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnNestedJoinedN
BSON("$lookup" << BSON("from" << testBarNss.coll() << "pipeline" << nestedPipeline)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnNestedJoinedNamespace) {
@@ -969,7 +1004,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnNestedJoinedNamespa
BSON("$lookup" << BSON("from" << testBarNss.coll() << "pipeline" << nestedPipeline)));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateWithDeeplyNestedLookup) {
@@ -1010,7 +1047,9 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateWithDeeplyNestedLookup) {
pipelineBuilder.doneFast();
cmdBuilder << "cursor" << BSONObj();
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdBuilder.obj(), false));
+ PrivilegeVector privileges = uassertStatusOK(
+ authzSession->getPrivilegesForAggregate(testFooNss, cmdBuilder.obj(), false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
@@ -1020,8 +1059,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateGraphLookupWithoutFindOnJoinedNa
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespace) {
@@ -1031,7 +1071,9 @@ TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespac
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest,
@@ -1044,20 +1086,19 @@ TEST_F(AuthorizationSessionTest,
"[{$graphLookup: {from: 'qux'}}]}}"));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
// We have find on the $lookup namespace but not on the $graphLookup namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::find})});
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
// We have find on the $graphLookup namespace but not on the $lookup namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testQuxCollResource, {ActionType::find})});
- ASSERT_EQ(ErrorCodes::Unauthorized,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest,
@@ -1071,7 +1112,9 @@ TEST_F(AuthorizationSessionTest,
"[{$graphLookup: {from: 'qux'}}]}}"));
BSONObj cmdObj =
BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ PrivilegeVector privileges =
+ uassertStatusOK(authzSession->getPrivilegesForAggregate(testFooNss, cmdObj, true));
+ ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges));
}
TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithEmptyUserSet) {
@@ -1225,9 +1268,12 @@ TEST_F(AuthorizationSessionTest,
TEST_F(AuthorizationSessionTest, CannotListCollectionsWithoutListCollectionsPrivilege) {
BSONObj cmd = BSON("listCollections" << 1);
// With no privileges, there is not authorization to list collections
- ASSERT_FALSE(authzSession->isAuthorizedToListCollections(testFooNss.db(), cmd));
- ASSERT_FALSE(authzSession->isAuthorizedToListCollections(testBarNss.db(), cmd));
- ASSERT_FALSE(authzSession->isAuthorizedToListCollections(testQuxNss.db(), cmd));
+ ASSERT_EQ(ErrorCodes::Unauthorized,
+ authzSession->checkAuthorizedToListCollections(testFooNss.db(), cmd).getStatus());
+ ASSERT_EQ(ErrorCodes::Unauthorized,
+ authzSession->checkAuthorizedToListCollections(testBarNss.db(), cmd).getStatus());
+ ASSERT_EQ(ErrorCodes::Unauthorized,
+ authzSession->checkAuthorizedToListCollections(testQuxNss.db(), cmd).getStatus());
}
TEST_F(AuthorizationSessionTest, CanListCollectionsWithListCollectionsPrivilege) {
@@ -1235,9 +1281,9 @@ TEST_F(AuthorizationSessionTest, CanListCollectionsWithListCollectionsPrivilege)
// The listCollections privilege authorizes the list collections command.
authzSession->assumePrivilegesForDB(Privilege(testDBResource, {ActionType::listCollections}));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testFooNss.db(), cmd));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testBarNss.db(), cmd));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testQuxNss.db(), cmd));
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testFooNss.db(), cmd).getStatus());
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testBarNss.db(), cmd).getStatus());
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testQuxNss.db(), cmd).getStatus());
}
TEST_F(AuthorizationSessionTest, CanListOwnCollectionsWithPrivilege) {
@@ -1246,11 +1292,12 @@ TEST_F(AuthorizationSessionTest, CanListOwnCollectionsWithPrivilege) {
// The listCollections privilege authorizes the list collections command.
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testFooNss.db(), cmd));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testBarNss.db(), cmd));
- ASSERT_TRUE(authzSession->isAuthorizedToListCollections(testQuxNss.db(), cmd));
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testFooNss.db(), cmd).getStatus());
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testBarNss.db(), cmd).getStatus());
+ ASSERT_OK(authzSession->checkAuthorizedToListCollections(testQuxNss.db(), cmd).getStatus());
- ASSERT_FALSE(authzSession->isAuthorizedToListCollections("other", cmd));
+ ASSERT_EQ(ErrorCodes::Unauthorized,
+ authzSession->checkAuthorizedToListCollections("other", cmd).getStatus());
}
TEST_F(AuthorizationSessionTest, CanCheckIfHasAnyPrivilegeOnResource) {
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp
index 41c2b814d5c..fef5105a9a5 100644
--- a/src/mongo/db/clientcursor.cpp
+++ b/src/mongo/db/clientcursor.cpp
@@ -90,6 +90,7 @@ ClientCursor::ClientCursor(ClientCursorParams params,
_txnNumber(operationUsingCursor->getTxnNumber()),
_readConcernArgs(params.readConcernArgs),
_originatingCommand(params.originatingCommandObj),
+ _originatingPrivileges(std::move(params.originatingPrivileges)),
_queryOptions(params.queryOptions),
_lockPolicy(params.lockPolicy),
_exec(std::move(params.exec)),
diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h
index 8bbc5491ee2..e4f447ddc59 100644
--- a/src/mongo/db/clientcursor.h
+++ b/src/mongo/db/clientcursor.h
@@ -32,6 +32,7 @@
#include <boost/optional.hpp>
+#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/jsobj.h"
@@ -80,7 +81,8 @@ struct ClientCursorParams {
UserNameIterator authenticatedUsersIter,
repl::ReadConcernArgs readConcernArgs,
BSONObj originatingCommandObj,
- LockPolicy lockPolicy)
+ LockPolicy lockPolicy,
+ PrivilegeVector originatingPrivileges)
: exec(std::move(planExecutor)),
nss(std::move(nss)),
readConcernArgs(readConcernArgs),
@@ -88,7 +90,8 @@ struct ClientCursorParams {
? exec->getCanonicalQuery()->getQueryRequest().getOptions()
: 0),
originatingCommandObj(originatingCommandObj.getOwned()),
- lockPolicy(lockPolicy) {
+ lockPolicy(lockPolicy),
+ originatingPrivileges(std::move(originatingPrivileges)) {
while (authenticatedUsersIter.more()) {
authenticatedUsers.emplace_back(authenticatedUsersIter.next());
}
@@ -115,6 +118,7 @@ struct ClientCursorParams {
int queryOptions = 0;
BSONObj originatingCommandObj;
const LockPolicy lockPolicy;
+ PrivilegeVector originatingPrivileges;
};
/**
@@ -186,11 +190,23 @@ public:
return _queryOptions & QueryOption_AwaitData;
}
+ /**
+ * Returns the original command object which created this cursor.
+ */
const BSONObj& getOriginatingCommandObj() const {
return _originatingCommand;
}
/**
+ * Returns the privileges required to run a getMore against this cursor. This is the same as the
+ * set of privileges which would have been required to create the cursor in the first place.
+ */
+ const PrivilegeVector& getOriginatingPrivileges() const& {
+ return _originatingPrivileges;
+ }
+ void getOriginatingPrivileges() && = delete;
+
+ /**
* Returns the total number of query results returned by the cursor so far.
*/
std::uint64_t nReturnedSoFar() const {
@@ -369,6 +385,9 @@ private:
// Holds an owned copy of the command specification received from the client.
const BSONObj _originatingCommand;
+ // The privileges required for the _originatingCommand.
+ const PrivilegeVector _originatingPrivileges;
+
// See the QueryOptions enum in dbclientinterface.h.
const int _queryOptions = 0;
diff --git a/src/mongo/db/commands/count_cmd.cpp b/src/mongo/db/commands/count_cmd.cpp
index a6b323b11f7..818ea1400e4 100644
--- a/src/mongo/db/commands/count_cmd.cpp
+++ b/src/mongo/db/commands/count_cmd.cpp
@@ -146,10 +146,13 @@ public:
return viewAggRequest.getStatus();
}
+ // An empty PrivilegeVector is acceptable because these privileges are only checked on
+ // getMore and explain will not open a cursor.
return runAggregate(opCtx,
viewAggRequest.getValue().getNamespaceString(),
viewAggRequest.getValue(),
viewAggregation.getValue(),
+ PrivilegeVector(),
result);
}
diff --git a/src/mongo/db/commands/current_op.cpp b/src/mongo/db/commands/current_op.cpp
index 0e76cfcd4a9..28eed7396e7 100644
--- a/src/mongo/db/commands/current_op.cpp
+++ b/src/mongo/db/commands/current_op.cpp
@@ -70,8 +70,17 @@ public:
rpc::OpMsgReplyBuilder replyBuilder;
- auto status = runAggregate(
- opCtx, request.getNamespaceString(), request, std::move(aggCmdObj), &replyBuilder);
+ PrivilegeVector privileges;
+ if (!aggCmdObj["$ownOps"].trueValue()) {
+ privileges = {Privilege(ResourcePattern::forClusterResource(), ActionType::inprog)};
+ }
+
+ auto status = runAggregate(opCtx,
+ request.getNamespaceString(),
+ request,
+ std::move(aggCmdObj),
+ privileges,
+ &replyBuilder);
if (!status.isOK()) {
return status;
diff --git a/src/mongo/db/commands/distinct.cpp b/src/mongo/db/commands/distinct.cpp
index 4d0f1dd70ea..f26feb2cf80 100644
--- a/src/mongo/db/commands/distinct.cpp
+++ b/src/mongo/db/commands/distinct.cpp
@@ -144,8 +144,14 @@ public:
return viewAggRequest.getStatus();
}
- return runAggregate(
- opCtx, nss, viewAggRequest.getValue(), viewAggregation.getValue(), result);
+ // An empty PrivilegeVector is acceptable because these privileges are only checked on
+ // getMore and explain will not open a cursor.
+ return runAggregate(opCtx,
+ nss,
+ viewAggRequest.getValue(),
+ viewAggregation.getValue(),
+ PrivilegeVector(),
+ result);
}
Collection* const collection = ctx->getCollection();
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index cfcdecc9fb8..e9a20684a6b 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -192,8 +192,10 @@ public:
AggregationRequest::parseFromBSON(nss, viewAggregationCommand, verbosity));
try {
- uassertStatusOK(
- runAggregate(opCtx, nss, aggRequest, viewAggregationCommand, result));
+ // An empty PrivilegeVector is acceptable because these privileges are only
+ // checked on getMore and explain will not open a cursor.
+ uassertStatusOK(runAggregate(
+ opCtx, nss, aggRequest, viewAggregationCommand, PrivilegeVector(), result));
} catch (DBException& error) {
if (error.code() == ErrorCodes::InvalidPipelineOperator) {
uasserted(ErrorCodes::InvalidPipelineOperator,
@@ -404,7 +406,8 @@ public:
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
repl::ReadConcernArgs::get(opCtx),
_request.body,
- ClientCursorParams::LockPolicy::kLockExternally});
+ ClientCursorParams::LockPolicy::kLockExternally,
+ {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)}});
cursorId = pinnedCursor.getCursor()->cursorid();
invariant(!exec);
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 8db406d77e2..c91eef49164 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -363,13 +363,20 @@ 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(cursorPin->getAuthenticatedUsers())) {
+ auto authzSession = AuthorizationSession::get(opCtx->getClient());
+ if (!authzSession->isCoauthorizedWith(cursorPin->getAuthenticatedUsers())) {
uasserted(ErrorCodes::Unauthorized,
str::stream() << "cursor id " << _request.cursorid
<< " was not created by the authenticated user");
}
+ // Ensure that the client still has the privileges to run the originating command.
+ if (!authzSession->isAuthorizedForPrivileges(cursorPin->getOriginatingPrivileges())) {
+ uasserted(ErrorCodes::Unauthorized,
+ str::stream() << "not authorized for getMore with cursor id "
+ << _request.cursorid);
+ }
+
if (_request.nss != cursorPin->nss()) {
uasserted(ErrorCodes::Unauthorized,
str::stream() << "Requested getMore on namespace '" << _request.nss.ns()
diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp
index 4277b8455aa..a702a8f6807 100644
--- a/src/mongo/db/commands/list_collections.cpp
+++ b/src/mongo/db/commands/list_collections.cpp
@@ -233,7 +233,7 @@ public:
AuthorizationSession* authzSession = AuthorizationSession::get(client);
- if (authzSession->isAuthorizedToListCollections(dbname, cmdObj)) {
+ if (authzSession->checkAuthorizedToListCollections(dbname, cmdObj).isOK()) {
return Status::OK();
}
@@ -386,7 +386,9 @@ public:
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
repl::ReadConcernArgs::get(opCtx),
jsobj,
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
+ ->checkAuthorizedToListCollections(dbname, jsobj))});
appendCursorResponseObject(
pinnedCursor.getCursor()->cursorid(), cursorNss.ns(), firstBatch.arr(), &result);
diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp
index b1d2796bb31..2210639210d 100644
--- a/src/mongo/db/commands/list_indexes.cpp
+++ b/src/mongo/db/commands/list_indexes.cpp
@@ -226,7 +226,8 @@ public:
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
repl::ReadConcernArgs::get(opCtx),
cmdObj,
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::listIndexes)}});
appendCursorResponseObject(
pinnedCursor.getCursor()->cursorid(), nss.ns(), firstBatch.arr(), &result);
diff --git a/src/mongo/db/commands/pipeline_command.cpp b/src/mongo/db/commands/pipeline_command.cpp
index edcd4af42b1..ef5dc99606b 100644
--- a/src/mongo/db/commands/pipeline_command.cpp
+++ b/src/mongo/db/commands/pipeline_command.cpp
@@ -53,15 +53,24 @@ public:
std::unique_ptr<CommandInvocation> parse(OperationContext* opCtx,
const OpMsgRequest& opMsgRequest) override {
// TODO: Parsing to a Pipeline and/or AggregationRequest here.
- return std::make_unique<Invocation>(this, opMsgRequest);
+
+ auto privileges =
+ uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
+ ->getPrivilegesForAggregate(
+ AggregationRequest::parseNs(
+ opMsgRequest.getDatabase().toString(), opMsgRequest.body),
+ opMsgRequest.body,
+ false));
+ return std::make_unique<Invocation>(this, opMsgRequest, std::move(privileges));
}
class Invocation final : public CommandInvocation {
public:
- Invocation(Command* cmd, const OpMsgRequest& request)
+ Invocation(Command* cmd, const OpMsgRequest& request, PrivilegeVector privileges)
: CommandInvocation(cmd),
_request(request),
- _dbName(request.getDatabase().toString()) {}
+ _dbName(request.getDatabase().toString()),
+ _privileges(std::move(privileges)) {}
private:
bool supportsWriteConcern() const override {
@@ -90,11 +99,11 @@ public:
void run(OperationContext* opCtx, rpc::ReplyBuilderInterface* reply) override {
const auto aggregationRequest = uassertStatusOK(
AggregationRequest::parseFromBSON(_dbName, _request.body, boost::none));
-
uassertStatusOK(runAggregate(opCtx,
aggregationRequest.getNamespaceString(),
aggregationRequest,
_request.body,
+ _privileges,
reply));
}
@@ -112,17 +121,20 @@ public:
aggregationRequest.getNamespaceString(),
aggregationRequest,
_request.body,
+ _privileges,
result));
}
void doCheckAuthorization(OperationContext* opCtx) const override {
- const auto nss = ns();
- uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
- ->checkAuthForAggregate(nss, _request.body, false));
+ uassert(ErrorCodes::Unauthorized,
+ "unauthorized",
+ AuthorizationSession::get(opCtx->getClient())
+ ->isAuthorizedForPrivileges(_privileges));
}
const OpMsgRequest& _request;
const std::string _dbName;
+ const PrivilegeVector _privileges;
};
std::string help() const override {
diff --git a/src/mongo/db/commands/repair_cursor.cpp b/src/mongo/db/commands/repair_cursor.cpp
index b21d2c2e23c..04c4d29fc49 100644
--- a/src/mongo/db/commands/repair_cursor.cpp
+++ b/src/mongo/db/commands/repair_cursor.cpp
@@ -108,7 +108,8 @@ public:
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
repl::ReadConcernArgs::get(opCtx),
cmdObj,
- ClientCursorParams::LockPolicy::kLockExternally});
+ ClientCursorParams::LockPolicy::kLockExternally,
+ {Privilege(parseResourcePattern(dbname, cmdObj), ActionType::find)}});
appendCursorResponseObject(
pinnedCursor.getCursor()->cursorid(), ns.ns(), BSONArray(), &result);
diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp
index 09ac63f0349..a8cefeb2caf 100644
--- a/src/mongo/db/commands/run_aggregate.cpp
+++ b/src/mongo/db/commands/run_aggregate.cpp
@@ -392,6 +392,7 @@ Status runAggregate(OperationContext* opCtx,
const NamespaceString& origNss,
const AggregationRequest& request,
const BSONObj& cmdObj,
+ const PrivilegeVector& privileges,
rpc::ReplyBuilderInterface* result) {
// For operations on views, this will be the underlying namespace.
NamespaceString nss = request.getNamespaceString();
@@ -516,7 +517,7 @@ Status runAggregate(OperationContext* opCtx,
auto newRequest = resolvedView.asExpandedViewAggregation(request);
auto newCmd = newRequest.serializeToCommandObj().toBson();
- auto status = runAggregate(opCtx, origNss, newRequest, newCmd, result);
+ auto status = runAggregate(opCtx, origNss, newRequest, newCmd, privileges, result);
{
// Set the namespace of the curop back to the view namespace so ctx records
@@ -628,7 +629,6 @@ Status runAggregate(OperationContext* opCtx,
p.deleteUnderlying();
}
});
-
for (size_t idx = 0; idx < execs.size(); ++idx) {
ClientCursorParams cursorParams(
std::move(execs[idx]),
@@ -636,7 +636,8 @@ Status runAggregate(OperationContext* opCtx,
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
repl::ReadConcernArgs::get(opCtx),
cmdObj,
- ClientCursorParams::LockPolicy::kLocksInternally);
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ privileges);
if (expCtx->tailableMode == TailableModeEnum::kTailable) {
cursorParams.setTailable(true);
} else if (expCtx->tailableMode == TailableModeEnum::kTailableAndAwaitData) {
diff --git a/src/mongo/db/commands/run_aggregate.h b/src/mongo/db/commands/run_aggregate.h
index 941d6036009..983161ac568 100644
--- a/src/mongo/db/commands/run_aggregate.h
+++ b/src/mongo/db/commands/run_aggregate.h
@@ -32,6 +32,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/auth/privilege.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/pipeline/aggregation_request.h"
@@ -45,12 +46,16 @@ namespace mongo {
* The raw aggregate command parameters should be passed in 'cmdObj', and will be reported as the
* originatingCommand in subsequent getMores on the resulting agg cursor.
*
+ * 'privileges' contains the privileges that were required to run this aggregation, to be used later
+ * for re-checking privileges for getMore commands.
+ *
* On success, fills out 'result' with the command response.
*/
Status runAggregate(OperationContext* opCtx,
const NamespaceString& nss,
const AggregationRequest& request,
const BSONObj& cmdObj,
+ const PrivilegeVector& privileges,
rpc::ReplyBuilderInterface* result);
} // namespace mongo
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index f1cd1574494..b2b911c880a 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -1374,10 +1374,12 @@ public:
rpc::OpMsgReplyBuilder replyBuilder;
AggregationRequest aggRequest(AuthorizationManager::usersCollectionNamespace,
std::move(pipeline));
+ // Impose no cursor privilege requirements, as cursor is drained internally
uassertStatusOK(runAggregate(opCtx,
AuthorizationManager::usersCollectionNamespace,
aggRequest,
aggRequest.serializeToCommandObj().toBson(),
+ PrivilegeVector(),
&replyBuilder));
auto bodyBuilder = replyBuilder.getBodyBuilder();
CommandHelpers::appendSimpleCommandStatus(bodyBuilder, true);
diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp
index da3ff98fb0c..a6aa3fc48ac 100644
--- a/src/mongo/db/query/find.cpp
+++ b/src/mongo/db/query/find.cpp
@@ -676,7 +676,8 @@ std::string runQuery(OperationContext* opCtx,
AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(),
readConcernArgs,
upconvertedQuery,
- ClientCursorParams::LockPolicy::kLockExternally});
+ ClientCursorParams::LockPolicy::kLockExternally,
+ {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)}});
ccId = pinnedCursor.getCursor()->cursorid();
LOG(5) << "caching executor with cursorid " << ccId << " after returning " << numResults
diff --git a/src/mongo/dbtests/cursor_manager_test.cpp b/src/mongo/dbtests/cursor_manager_test.cpp
index 866881009e0..6de2ba0b3c4 100644
--- a/src/mongo/dbtests/cursor_manager_test.cpp
+++ b/src/mongo/dbtests/cursor_manager_test.cpp
@@ -87,7 +87,8 @@ public:
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally};
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()};
}
ClientCursorPin makeCursor(OperationContext* opCtx) {
@@ -136,7 +137,8 @@ TEST_F(CursorManagerTest, ShouldBeAbleToKillPinnedCursor) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
auto cursorId = cursorPin.getCursor()->cursorid();
ASSERT_OK(cursorManager->killCursor(_opCtx.get(), cursorId, shouldAudit));
@@ -161,7 +163,8 @@ TEST_F(CursorManagerTest, ShouldBeAbleToKillPinnedCursorMultiClient) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
auto cursorId = cursorPin.getCursor()->cursorid();
@@ -196,7 +199,8 @@ TEST_F(CursorManagerTest, InactiveCursorShouldTimeout) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
ASSERT_EQ(0UL, cursorManager->timeoutCursors(_opCtx.get(), Date_t()));
@@ -210,7 +214,8 @@ TEST_F(CursorManagerTest, InactiveCursorShouldTimeout) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
ASSERT_EQ(1UL, cursorManager->timeoutCursors(_opCtx.get(), Date_t::max()));
ASSERT_EQ(0UL, cursorManager->numCursors());
}
@@ -229,7 +234,8 @@ TEST_F(CursorManagerTest, InactivePinnedCursorShouldNotTimeout) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
// The pin is still in scope, so it should not time out.
clock->advance(getDefaultCursorTimeoutMillis());
@@ -252,7 +258,8 @@ TEST_F(CursorManagerTest, MarkedAsKilledCursorsShouldBeDeletedOnCursorPin) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
auto cursorId = cursorPin->cursorid();
// A cursor will stay alive, but be marked as killed, if it is interrupted with a code other
@@ -284,7 +291,8 @@ TEST_F(CursorManagerTest, InactiveKilledCursorsShouldTimeout) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
// A cursor will stay alive, but be marked as killed, if it is interrupted with a code other
// than ErrorCodes::Interrupted or ErrorCodes::CursorKilled and then unpinned.
@@ -315,7 +323,8 @@ TEST_F(CursorManagerTest, UsingACursorShouldUpdateTimeOfLastUse) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
auto usedCursorId = cursorPin.getCursor()->cursorid();
cursorPin.release();
@@ -327,7 +336,8 @@ TEST_F(CursorManagerTest, UsingACursorShouldUpdateTimeOfLastUse) {
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
// Advance the clock to simulate time passing.
clock->advance(Milliseconds(1));
@@ -363,7 +373,8 @@ TEST_F(CursorManagerTest, CursorShouldNotTimeOutUntilIdleForLongEnoughAfterBeing
{},
repl::ReadConcernArgs(repl::ReadConcernLevel::kLocalReadConcern),
BSONObj(),
- ClientCursorParams::LockPolicy::kLocksInternally});
+ ClientCursorParams::LockPolicy::kLocksInternally,
+ PrivilegeVector()});
// Advance the clock to simulate time passing.
clock->advance(getDefaultCursorTimeoutMillis() + Milliseconds(1));
diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp
index a3ea0de15d7..1171aa653b0 100644
--- a/src/mongo/embedded/embedded_auth_session.cpp
+++ b/src/mongo/embedded/embedded_auth_session.cpp
@@ -135,8 +135,10 @@ public:
return Status::OK();
}
- Status checkAuthForAggregate(const NamespaceString&, const BSONObj&, bool) override {
- return Status::OK();
+ StatusWith<PrivilegeVector> getPrivilegesForAggregate(const NamespaceString&,
+ const BSONObj&,
+ bool) override {
+ return PrivilegeVector();
}
Status checkAuthForCreate(const NamespaceString&, const BSONObj&, bool) override {
@@ -155,6 +157,11 @@ public:
return Status::OK();
}
+ StatusWith<PrivilegeVector> checkAuthorizedToListCollections(StringData,
+ const BSONObj&) override {
+ return PrivilegeVector();
+ }
+
bool isUsingLocalhostBypass() override {
return false;
}
@@ -183,10 +190,6 @@ public:
return true;
}
- bool isAuthorizedToListCollections(StringData, const BSONObj&) override {
- return true;
- }
-
bool isAuthorizedToChangeOwnCustomDataAsUser(const UserName&) override {
return true;
}
diff --git a/src/mongo/s/commands/cluster_count_cmd.cpp b/src/mongo/s/commands/cluster_count_cmd.cpp
index 58b54948b52..ce1f3dd5b96 100644
--- a/src/mongo/s/commands/cluster_count_cmd.cpp
+++ b/src/mongo/s/commands/cluster_count_cmd.cpp
@@ -274,10 +274,13 @@ public:
}
auto bodyBuilder = result->getBodyBuilder();
+ // An empty PrivilegeVector is acceptable because these privileges are only checked on
+ // getMore and explain will not open a cursor.
return ClusterAggregate::retryOnViewError(opCtx,
aggRequestOnView.getValue(),
*ex.extraInfo<ResolvedView>(),
nss,
+ PrivilegeVector(),
&bodyBuilder);
}
diff --git a/src/mongo/s/commands/cluster_current_op.cpp b/src/mongo/s/commands/cluster_current_op.cpp
index 84e527fde77..20ae433ced4 100644
--- a/src/mongo/s/commands/cluster_current_op.cpp
+++ b/src/mongo/s/commands/cluster_current_op.cpp
@@ -77,7 +77,11 @@ private:
BSONObjBuilder responseBuilder;
auto status = ClusterAggregate::runAggregate(
- opCtx, ClusterAggregate::Namespaces{nss, nss}, request, &responseBuilder);
+ opCtx,
+ ClusterAggregate::Namespaces{nss, nss},
+ request,
+ {Privilege(ResourcePattern::forClusterResource(), ActionType::inprog)},
+ &responseBuilder);
if (!status.isOK()) {
return status;
diff --git a/src/mongo/s/commands/cluster_distinct_cmd.cpp b/src/mongo/s/commands/cluster_distinct_cmd.cpp
index 46cfcaf1f95..0897f53dfa4 100644
--- a/src/mongo/s/commands/cluster_distinct_cmd.cpp
+++ b/src/mongo/s/commands/cluster_distinct_cmd.cpp
@@ -135,10 +135,13 @@ public:
}
auto bodyBuilder = result->getBodyBuilder();
+ // An empty PrivilegeVector is acceptable because these privileges are only checked on
+ // getMore and explain will not open a cursor.
return ClusterAggregate::retryOnViewError(opCtx,
aggRequestOnView.getValue(),
*ex.extraInfo<ResolvedView>(),
nss,
+ PrivilegeVector(),
&bodyBuilder);
}
diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp
index e838190e51a..64d5917db25 100644
--- a/src/mongo/s/commands/cluster_find_cmd.cpp
+++ b/src/mongo/s/commands/cluster_find_cmd.cpp
@@ -169,8 +169,14 @@ public:
auto aggRequestOnView = uassertStatusOK(
AggregationRequest::parseFromBSON(ns(), aggCmdOnView, verbosity));
- uassertStatusOK(ClusterAggregate::retryOnViewError(
- opCtx, aggRequestOnView, *ex.extraInfo<ResolvedView>(), ns(), &bodyBuilder));
+ // An empty PrivilegeVector is acceptable because these privileges are only checked
+ // on getMore and explain will not open a cursor.
+ uassertStatusOK(ClusterAggregate::retryOnViewError(opCtx,
+ aggRequestOnView,
+ *ex.extraInfo<ResolvedView>(),
+ ns(),
+ PrivilegeVector(),
+ &bodyBuilder));
}
}
@@ -215,7 +221,12 @@ public:
auto bodyBuilder = result->getBodyBuilder();
uassertStatusOK(ClusterAggregate::retryOnViewError(
- opCtx, aggRequestOnView, *ex.extraInfo<ResolvedView>(), ns(), &bodyBuilder));
+ opCtx,
+ aggRequestOnView,
+ *ex.extraInfo<ResolvedView>(),
+ ns(),
+ {Privilege(ResourcePattern::forExactNamespace(ns()), ActionType::find)},
+ &bodyBuilder));
}
}
diff --git a/src/mongo/s/commands/cluster_pipeline_cmd.cpp b/src/mongo/s/commands/cluster_pipeline_cmd.cpp
index f1cb773895f..ce8e5dfd15e 100644
--- a/src/mongo/s/commands/cluster_pipeline_cmd.cpp
+++ b/src/mongo/s/commands/cluster_pipeline_cmd.cpp
@@ -47,16 +47,24 @@ public:
std::unique_ptr<CommandInvocation> parse(OperationContext* opCtx,
const OpMsgRequest& opMsgRequest) override {
+ auto privileges =
+ uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
+ ->getPrivilegesForAggregate(
+ AggregationRequest::parseNs(
+ opMsgRequest.getDatabase().toString(), opMsgRequest.body),
+ opMsgRequest.body,
+ true));
// TODO: Parsing to a Pipeline and/or AggregationRequest here.
- return std::make_unique<Invocation>(this, opMsgRequest);
+ return std::make_unique<Invocation>(this, opMsgRequest, std::move(privileges));
}
class Invocation final : public CommandInvocation {
public:
- Invocation(Command* cmd, const OpMsgRequest& request)
+ Invocation(Command* cmd, const OpMsgRequest& request, PrivilegeVector privileges)
: CommandInvocation(cmd),
_request(request),
- _dbName(request.getDatabase().toString()) {}
+ _dbName(request.getDatabase().toString()),
+ _privileges(std::move(privileges)) {}
private:
bool supportsWriteConcern() const override {
@@ -67,24 +75,31 @@ public:
return true;
}
- static void _runAggCommand(OperationContext* opCtx,
- const std::string& dbname,
- const BSONObj& cmdObj,
- boost::optional<ExplainOptions::Verbosity> verbosity,
- BSONObjBuilder* result) {
+ void _runAggCommand(OperationContext* opCtx,
+ const std::string& dbname,
+ const BSONObj& cmdObj,
+ boost::optional<ExplainOptions::Verbosity> verbosity,
+ BSONObjBuilder* result) {
const auto aggregationRequest =
uassertStatusOK(AggregationRequest::parseFromBSON(dbname, cmdObj, verbosity));
-
const auto& nss = aggregationRequest.getNamespaceString();
try {
- uassertStatusOK(ClusterAggregate::runAggregate(
- opCtx, ClusterAggregate::Namespaces{nss, nss}, aggregationRequest, result));
+ uassertStatusOK(
+ ClusterAggregate::runAggregate(opCtx,
+ ClusterAggregate::Namespaces{nss, nss},
+ aggregationRequest,
+ _privileges,
+ result));
} catch (const ExceptionFor<ErrorCodes::CommandOnShardedViewNotSupportedOnMongod>& ex) {
// If the aggregation failed because the namespace is a view, re-run the command
// with the resolved view pipeline and namespace.
- uassertStatusOK(ClusterAggregate::retryOnViewError(
- opCtx, aggregationRequest, *ex.extraInfo<ResolvedView>(), nss, result));
+ uassertStatusOK(ClusterAggregate::retryOnViewError(opCtx,
+ aggregationRequest,
+ *ex.extraInfo<ResolvedView>(),
+ nss,
+ _privileges,
+ result));
}
}
@@ -101,9 +116,10 @@ public:
}
void doCheckAuthorization(OperationContext* opCtx) const override {
- const auto nss = ns();
- uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
- ->checkAuthForAggregate(nss, _request.body, true));
+ uassert(ErrorCodes::Unauthorized,
+ "unauthorized",
+ AuthorizationSession::get(opCtx->getClient())
+ ->isAuthorizedForPrivileges(_privileges));
}
NamespaceString ns() const override {
@@ -112,6 +128,7 @@ public:
const OpMsgRequest& _request;
const std::string _dbName;
+ const PrivilegeVector _privileges;
};
std::string help() const override {
diff --git a/src/mongo/s/commands/commands_public.cpp b/src/mongo/s/commands/commands_public.cpp
index eb0a9f75704..d6f939f78b2 100644
--- a/src/mongo/s/commands/commands_public.cpp
+++ b/src/mongo/s/commands/commands_public.cpp
@@ -57,7 +57,8 @@ bool cursorCommandPassthrough(OperationContext* opCtx,
const CachedDatabaseInfo& dbInfo,
const BSONObj& cmdObj,
const NamespaceString& nss,
- BSONObjBuilder* out) {
+ BSONObjBuilder* out,
+ const PrivilegeVector& privileges) {
auto response = executeCommandAgainstDatabasePrimary(
opCtx,
dbName,
@@ -74,7 +75,8 @@ bool cursorCommandPassthrough(OperationContext* opCtx,
cmdResponse.data,
nss,
Grid::get(opCtx)->getExecutorPool()->getArbitraryExecutor(),
- Grid::get(opCtx)->getCursorManager()));
+ Grid::get(opCtx)->getCursorManager(),
+ privileges));
CommandHelpers::filterCommandReplyForPassthrough(transformedResponse, out);
return true;
@@ -338,7 +340,7 @@ public:
const BSONObj& cmdObj) const final {
AuthorizationSession* authzSession = AuthorizationSession::get(client);
- if (authzSession->isAuthorizedToListCollections(dbname, cmdObj)) {
+ if (authzSession->checkAuthorizedToListCollections(dbname, cmdObj).isOK()) {
return Status::OK();
}
@@ -447,9 +449,15 @@ public:
}
return cursorCommandPassthrough(
- opCtx, dbName, dbInfoStatus.getValue(), newCmd, nss, &result);
+ opCtx,
+ dbName,
+ dbInfoStatus.getValue(),
+ newCmd,
+ nss,
+ &result,
+ uassertStatusOK(AuthorizationSession::get(opCtx->getClient())
+ ->checkAuthorizedToListCollections(dbName, cmdObj)));
}
-
} cmdListCollections;
class CmdListIndexes : public BasicCommand {
@@ -498,7 +506,14 @@ public:
const auto routingInfo =
uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfo(opCtx, nss));
- return cursorCommandPassthrough(opCtx, nss.db(), routingInfo.db(), cmdObj, nss, &result);
+ return cursorCommandPassthrough(
+ opCtx,
+ nss.db(),
+ routingInfo.db(),
+ cmdObj,
+ nss,
+ &result,
+ {Privilege(ResourcePattern::forExactNamespace(nss), ActionType::listIndexes)});
}
} cmdListIndexes;
diff --git a/src/mongo/s/query/cluster_aggregate.cpp b/src/mongo/s/query/cluster_aggregate.cpp
index 637df1c1b3c..f5de45fcec5 100644
--- a/src/mongo/s/query/cluster_aggregate.cpp
+++ b/src/mongo/s/query/cluster_aggregate.cpp
@@ -293,12 +293,12 @@ Shard::CommandResponse establishMergingShardCursor(OperationContext* opCtx,
opCtx, ReadPreferenceSetting::get(opCtx), nss.db().toString(), mergeCmdObj, retryPolicy));
}
-BSONObj establishMergingMongosCursor(
- OperationContext* opCtx,
- const AggregationRequest& request,
- const NamespaceString& requestedNss,
- const LiteParsedPipeline& liteParsedPipeline,
- std::unique_ptr<Pipeline, PipelineDeleter> pipelineForMerging) {
+BSONObj establishMergingMongosCursor(OperationContext* opCtx,
+ const AggregationRequest& request,
+ const NamespaceString& requestedNss,
+ const LiteParsedPipeline& liteParsedPipeline,
+ std::unique_ptr<Pipeline, PipelineDeleter> pipelineForMerging,
+ const PrivilegeVector& privileges) {
ClusterClientCursorParams params(requestedNss, ReadPreferenceSetting::get(opCtx));
@@ -312,6 +312,7 @@ BSONObj establishMergingMongosCursor(
: boost::optional<long long>(request.getBatchSize());
params.lsid = opCtx->getLogicalSessionId();
params.txnNumber = opCtx->getTxnNumber();
+ params.originatingPrivileges = privileges;
if (TransactionRouter::get(opCtx)) {
params.isAutoCommit = false;
@@ -577,7 +578,8 @@ Status runPipelineOnMongoS(const boost::intrusive_ptr<ExpressionContext>& expCtx
const AggregationRequest& request,
const LiteParsedPipeline& litePipe,
std::unique_ptr<Pipeline, PipelineDeleter> pipeline,
- BSONObjBuilder* result) {
+ BSONObjBuilder* result,
+ const PrivilegeVector& privileges) {
// We should never receive a pipeline which cannot run on mongoS.
invariant(!expCtx->explain);
invariant(pipeline->canRunOnMongos());
@@ -593,8 +595,8 @@ Status runPipelineOnMongoS(const boost::intrusive_ptr<ExpressionContext>& expCtx
!pipeline->getSources().front()->constraints().requiresInputDocSource);
// Register the new mongoS cursor, and retrieve the initial batch of results.
- auto cursorResponse =
- establishMergingMongosCursor(opCtx, request, requestedNss, litePipe, std::move(pipeline));
+ auto cursorResponse = establishMergingMongosCursor(
+ opCtx, request, requestedNss, litePipe, std::move(pipeline), privileges);
// We don't need to storePossibleCursor or propagate writeConcern errors; an $out pipeline
// can never run on mongoS. Filter the command response and return immediately.
@@ -609,7 +611,8 @@ Status dispatchMergingPipeline(
const LiteParsedPipeline& litePipe,
const boost::optional<CachedCollectionRoutingInfo>& routingInfo,
sharded_agg_helpers::DispatchShardPipelineResults&& shardDispatchResults,
- BSONObjBuilder* result) {
+ BSONObjBuilder* result,
+ const PrivilegeVector& privileges) {
// We should never be in a situation where we call this function on a non-merge pipeline.
invariant(shardDispatchResults.splitPipeline);
auto* mergePipeline = shardDispatchResults.splitPipeline->mergePipeline.get();
@@ -640,7 +643,8 @@ Status dispatchMergingPipeline(
request,
litePipe,
std::move(shardDispatchResults.splitPipeline->mergePipeline),
- result);
+ result,
+ privileges);
}
// If we are not merging on mongoS, then this is not a $changeStream aggregation, and we
@@ -662,8 +666,12 @@ Status dispatchMergingPipeline(
auto mergeResponse = establishMergingShardCursor(
opCtx, namespaces.executionNss, request, mergeCmdObj, mergingShardId);
- auto mergeCursorResponse = uassertStatusOK(storePossibleCursor(
- opCtx, namespaces.requestedNss, mergingShardId, mergeResponse, expCtx->tailableMode));
+ auto mergeCursorResponse = uassertStatusOK(storePossibleCursor(opCtx,
+ namespaces.requestedNss,
+ mergingShardId,
+ mergeResponse,
+ privileges,
+ expCtx->tailableMode));
// Ownership for the shard cursors has been transferred to the merging shard. Dismiss the
// ownership in the current merging pipeline such that when it goes out of scope it does not
@@ -690,6 +698,7 @@ void appendEmptyResultSetWithStatus(OperationContext* opCtx,
Status ClusterAggregate::runAggregate(OperationContext* opCtx,
const Namespaces& namespaces,
const AggregationRequest& request,
+ const PrivilegeVector& privileges,
BSONObjBuilder* result) {
uassert(51028, "Cannot specify exchange option to a mongos", !request.getExchangeSpec());
auto executionNsRoutingInfoStatus =
@@ -739,7 +748,8 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx,
litePipe.allowedToForwardFromMongos() && litePipe.allowedToPassthroughFromMongos() &&
!involvesShardedCollections) {
const auto primaryShardId = routingInfo->db().primary()->getId();
- return aggPassthrough(opCtx, namespaces, primaryShardId, request, litePipe, result);
+ return aggPassthrough(
+ opCtx, namespaces, primaryShardId, request, litePipe, privileges, result);
}
// Populate the collection UUID and the appropriate collation to use.
@@ -768,7 +778,7 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx,
}
return runPipelineOnMongoS(
- expCtx, namespaces, request, litePipe, std::move(pipeline), result);
+ expCtx, namespaces, request, litePipe, std::move(pipeline), result, privileges);
}
// If not, split the pipeline as necessary and dispatch to the relevant shards.
@@ -790,8 +800,11 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx,
invariant(shardDispatchResults.remoteCursors.size() == 1);
auto&& remoteCursor = std::move(shardDispatchResults.remoteCursors.front());
const auto shardId = remoteCursor->getShardId().toString();
- const auto reply = uassertStatusOK(storePossibleCursor(
- opCtx, namespaces.requestedNss, std::move(remoteCursor), expCtx->tailableMode));
+ const auto reply = uassertStatusOK(storePossibleCursor(opCtx,
+ namespaces.requestedNss,
+ std::move(remoteCursor),
+ privileges,
+ expCtx->tailableMode));
return appendCursorResponseToCommandResult(shardId, reply, result);
}
@@ -812,7 +825,8 @@ Status ClusterAggregate::runAggregate(OperationContext* opCtx,
litePipe,
routingInfo,
std::move(shardDispatchResults),
- result);
+ result,
+ privileges);
}
void ClusterAggregate::uassertAllShardsSupportExplain(
@@ -839,6 +853,7 @@ Status ClusterAggregate::aggPassthrough(OperationContext* opCtx,
const ShardId& shardId,
const AggregationRequest& aggRequest,
const LiteParsedPipeline& liteParsedPipeline,
+ const PrivilegeVector& privileges,
BSONObjBuilder* out) {
// Temporary hack. See comment on declaration for details.
auto swShard = Grid::get(opCtx)->shardRegistry()->getShard(opCtx, shardId);
@@ -878,7 +893,7 @@ Status ClusterAggregate::aggPassthrough(OperationContext* opCtx,
? TailableModeEnum::kTailableAndAwaitData
: TailableModeEnum::kNormal;
result = uassertStatusOK(storePossibleCursor(
- opCtx, namespaces.requestedNss, shard->getId(), cmdResponse, tailMode));
+ opCtx, namespaces.requestedNss, shard->getId(), cmdResponse, privileges, tailMode));
}
// First append the properly constructed writeConcernError. It will then be skipped
@@ -896,6 +911,7 @@ Status ClusterAggregate::retryOnViewError(OperationContext* opCtx,
const AggregationRequest& request,
const ResolvedView& resolvedView,
const NamespaceString& requestedNss,
+ const PrivilegeVector& privileges,
BSONObjBuilder* result,
unsigned numberRetries) {
if (numberRetries >= kMaxViewRetries) {
@@ -918,7 +934,8 @@ Status ClusterAggregate::retryOnViewError(OperationContext* opCtx,
nsStruct.requestedNss = requestedNss;
nsStruct.executionNss = resolvedView.getNamespace();
- auto status = ClusterAggregate::runAggregate(opCtx, nsStruct, resolvedAggRequest, result);
+ auto status =
+ ClusterAggregate::runAggregate(opCtx, nsStruct, resolvedAggRequest, privileges, result);
// If the underlying namespace was changed to a view during retry, then re-run the aggregation
// on the new resolved namespace.
@@ -927,6 +944,7 @@ Status ClusterAggregate::retryOnViewError(OperationContext* opCtx,
resolvedAggRequest,
*status.extraInfo<ResolvedView>(),
requestedNss,
+ privileges,
result,
numberRetries + 1);
}
diff --git a/src/mongo/s/query/cluster_aggregate.h b/src/mongo/s/query/cluster_aggregate.h
index 822ad3cc078..c2647d65656 100644
--- a/src/mongo/s/query/cluster_aggregate.h
+++ b/src/mongo/s/query/cluster_aggregate.h
@@ -77,23 +77,31 @@ public:
* over which the aggregation will actually execute. Typically these two namespaces are the
* same, but they may differ in the case of a query on a view.
*
+ * 'privileges' contains the privileges that were required to run this aggregation, to be used
+ * later for re-checking privileges for GetMore commands.
+ *
* On success, fills out 'result' with the command response.
*/
static Status runAggregate(OperationContext* opCtx,
const Namespaces& namespaces,
const AggregationRequest& request,
+ const PrivilegeVector& privileges,
BSONObjBuilder* result);
/**
* Retries a command that was previously run on a view by resolving the view as an aggregation
* against the underlying collection.
*
+ * 'privileges' contains the privileges that were required to run this aggregation, to be used
+ * later for re-checking privileges for GetMore commands.
+ *
* On success, populates 'result' with the command response.
*/
static Status retryOnViewError(OperationContext* opCtx,
const AggregationRequest& request,
const ResolvedView& resolvedView,
const NamespaceString& requestedNss,
+ const PrivilegeVector& privileges,
BSONObjBuilder* result,
unsigned numberRetries = 0);
@@ -106,6 +114,7 @@ private:
const ShardId&,
const AggregationRequest&,
const LiteParsedPipeline&,
+ const PrivilegeVector& privileges,
BSONObjBuilder* result);
};
diff --git a/src/mongo/s/query/cluster_client_cursor.h b/src/mongo/s/query/cluster_client_cursor.h
index 06d7efb4abf..e9e3afb6b0f 100644
--- a/src/mongo/s/query/cluster_client_cursor.h
+++ b/src/mongo/s/query/cluster_client_cursor.h
@@ -113,6 +113,13 @@ public:
virtual BSONObj getOriginatingCommand() const = 0;
/**
+ * Returns the privileges required to run a getMore against this cursor. This is the same as the
+ * set of privileges which would have been required to create the cursor in the first place.
+ */
+ virtual const PrivilegeVector& getOriginatingPrivileges() const& = 0;
+ void getOriginatingPrivileges() && = delete;
+
+ /**
* Returns a reference to the vector of remote hosts involved in this operation.
*/
virtual std::size_t getNumRemotes() 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 3c620b77133..1d2e236ccb6 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_impl.cpp
@@ -158,6 +158,10 @@ BSONObj ClusterClientCursorImpl::getOriginatingCommand() const {
return _params.originatingCommandObj;
}
+const PrivilegeVector& ClusterClientCursorImpl::getOriginatingPrivileges() const& {
+ return _params.originatingPrivileges;
+}
+
std::size_t ClusterClientCursorImpl::getNumRemotes() const {
return _root->getNumRemotes();
}
diff --git a/src/mongo/s/query/cluster_client_cursor_impl.h b/src/mongo/s/query/cluster_client_cursor_impl.h
index c01cd89dfe3..77bc3d2b4e4 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.h
+++ b/src/mongo/s/query/cluster_client_cursor_impl.h
@@ -116,6 +116,9 @@ public:
BSONObj getOriginatingCommand() const final;
+ const PrivilegeVector& getOriginatingPrivileges() const& final;
+ void getOriginatingPrivileges() && = delete;
+
std::size_t getNumRemotes() const final;
BSONObj getPostBatchResumeToken() const final;
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.cpp b/src/mongo/s/query/cluster_client_cursor_mock.cpp
index 9fdc048819d..55c06c8cbcf 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_mock.cpp
@@ -71,6 +71,10 @@ BSONObj ClusterClientCursorMock::getOriginatingCommand() const {
return _originatingCommand;
}
+const PrivilegeVector& ClusterClientCursorMock::getOriginatingPrivileges() const& {
+ return _originatingPrivileges;
+}
+
std::size_t ClusterClientCursorMock::getNumRemotes() const {
MONGO_UNREACHABLE;
}
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.h b/src/mongo/s/query/cluster_client_cursor_mock.h
index 960cb59d218..a7ff38efbd0 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.h
+++ b/src/mongo/s/query/cluster_client_cursor_mock.h
@@ -72,6 +72,9 @@ public:
BSONObj getOriginatingCommand() const final;
+ const PrivilegeVector& getOriginatingPrivileges() const& final;
+ void getOriginatingPrivileges() && = delete;
+
std::size_t getNumRemotes() const final;
BSONObj getPostBatchResumeToken() const final;
@@ -120,6 +123,9 @@ private:
// Originating command object.
BSONObj _originatingCommand;
+ // Privileges of originating command
+ PrivilegeVector _originatingPrivileges;
+
// Number of returned documents.
long long _numReturnedSoFar = 0;
diff --git a/src/mongo/s/query/cluster_client_cursor_params.h b/src/mongo/s/query/cluster_client_cursor_params.h
index 7db927ed78e..37a0c9d2bbd 100644
--- a/src/mongo/s/query/cluster_client_cursor_params.h
+++ b/src/mongo/s/query/cluster_client_cursor_params.h
@@ -37,6 +37,7 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/client/read_preference.h"
+#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/namespace_string.h"
@@ -110,6 +111,9 @@ struct ClusterClientCursorParams {
// The original command object which generated this cursor. Must either be empty or owned.
BSONObj originatingCommandObj;
+ // The privileges required for the originatingCommand.
+ PrivilegeVector originatingPrivileges;
+
// Per-remote node data.
std::vector<RemoteCursor> remotes;
diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp
index 98cbb05f2b5..511217eea8c 100644
--- a/src/mongo/s/query/cluster_cursor_manager.cpp
+++ b/src/mongo/s/query/cluster_cursor_manager.cpp
@@ -149,6 +149,11 @@ BSONObj ClusterCursorManager::PinnedCursor::getOriginatingCommand() const {
return _cursor->getOriginatingCommand();
}
+const PrivilegeVector& ClusterCursorManager::PinnedCursor::getOriginatingPrivileges() const& {
+ invariant(_cursor);
+ return _cursor->getOriginatingPrivileges();
+}
+
std::size_t ClusterCursorManager::PinnedCursor::getNumRemotes() const {
invariant(_cursor);
return _cursor->getNumRemotes();
diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h
index 9e7d7a7b61c..886a43bce90 100644
--- a/src/mongo/s/query/cluster_cursor_manager.h
+++ b/src/mongo/s/query/cluster_cursor_manager.h
@@ -194,6 +194,13 @@ public:
BSONObj getOriginatingCommand() const;
/**
+ * Returns the privleges for the original command object which created this cursor.
+ */
+
+ const PrivilegeVector& getOriginatingPrivileges() const&;
+ void getOriginatingPrivileges() && = delete;
+
+ /**
* Returns a reference to the vector of remote hosts involved in this operation.
*/
std::size_t getNumRemotes() const;
diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp
index e9f55a271e6..4673003816e 100644
--- a/src/mongo/s/query/cluster_find.cpp
+++ b/src/mongo/s/query/cluster_find.cpp
@@ -233,6 +233,8 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx,
params.isAllowPartialResults = query.getQueryRequest().isAllowPartialResults();
params.lsid = opCtx->getLogicalSessionId();
params.txnNumber = opCtx->getTxnNumber();
+ params.originatingPrivileges = {
+ Privilege(ResourcePattern::forExactNamespace(query.nss()), ActionType::find)};
if (TransactionRouter::get(opCtx)) {
params.isAutoCommit = false;
@@ -565,6 +567,14 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx,
validateOperationSessionInfo(opCtx, request, &pinnedCursor.getValue());
+ // Ensure that the client still has the privileges to run the originating command.
+ if (!authzSession->isAuthorizedForPrivileges(
+ pinnedCursor.getValue().getOriginatingPrivileges())) {
+ uasserted(ErrorCodes::Unauthorized,
+ str::stream() << "not authorized for getMore with cursor id "
+ << request.cursorid);
+ }
+
// Set the originatingCommand object and the cursorID in CurOp.
{
CurOp::get(opCtx)->debug().nShards = pinnedCursor.getValue().getNumRemotes();
diff --git a/src/mongo/s/query/store_possible_cursor.cpp b/src/mongo/s/query/store_possible_cursor.cpp
index c0f04976bf9..28c052299b3 100644
--- a/src/mongo/s/query/store_possible_cursor.cpp
+++ b/src/mongo/s/query/store_possible_cursor.cpp
@@ -50,6 +50,7 @@ namespace mongo {
StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
OwnedRemoteCursor&& remoteCursor,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode) {
auto executorPool = Grid::get(opCtx)->getExecutorPool();
auto result = storePossibleCursor(
@@ -60,6 +61,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
requestedNss,
executorPool->getArbitraryExecutor(),
Grid::get(opCtx)->getCursorManager(),
+ std::move(privileges),
tailableMode);
// On success, release ownership of the cursor because it has been registered with the cursor
@@ -72,6 +74,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
const ShardId& shardId,
const Shard::CommandResponse& commandResponse,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode) {
invariant(commandResponse.hostAndPort);
auto executorPool = Grid::get(opCtx)->getExecutorPool();
@@ -82,6 +85,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
requestedNss,
executorPool->getArbitraryExecutor(),
Grid::get(opCtx)->getCursorManager(),
+ std::move(privileges),
tailableMode);
}
@@ -92,6 +96,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
executor::TaskExecutor* executor,
ClusterCursorManager* cursorManager,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode) {
if (!cmdResult["ok"].trueValue() || !cmdResult.hasField("cursor")) {
return cmdResult;
@@ -126,6 +131,7 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
params.tailableMode = tailableMode;
params.lsid = opCtx->getLogicalSessionId();
params.txnNumber = opCtx->getTxnNumber();
+ params.originatingPrivileges = std::move(privileges);
if (TransactionRouter::get(opCtx)) {
params.isAutoCommit = false;
diff --git a/src/mongo/s/query/store_possible_cursor.h b/src/mongo/s/query/store_possible_cursor.h
index 75c90738fe6..d145c76bcbf 100644
--- a/src/mongo/s/query/store_possible_cursor.h
+++ b/src/mongo/s/query/store_possible_cursor.h
@@ -30,6 +30,7 @@
#pragma once
+#include "mongo/db/auth/privilege.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/query/tailable_mode.h"
@@ -70,6 +71,8 @@ class TaskExecutor;
* differ from the execution namespace if the command was issued on a view)
* @ executor the TaskExecutor to store in the resulting ClusterClientCursor
* @ cursorManager the ClusterCursorManager on which to register the resulting ClusterClientCursor
+ * @ privileges the PrivilegeVector of privileges needed for the original command, to be used for
+ * auth checking by GetMore
*/
StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const ShardId& shardId,
@@ -78,25 +81,30 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
executor::TaskExecutor* executor,
ClusterCursorManager* cursorManager,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode = TailableModeEnum::kNormal);
/**
* Convenience function which extracts all necessary information from the passed RemoteCursor, and
* stores a ClusterClientCursor based on it. The ownership of the remote cursor is transferred to
- * this function, and will handle killing it upon failure.
+ * this function, and will handle killing it upon failure. 'privileges' contains the required
+ * privileges for the command, to be used by GetMore for auth checks.
*/
StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
OwnedRemoteCursor&& remoteCursor,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode);
/**
* Convenience function which extracts all necessary information from the passed CommandResponse,
- * and stores a ClusterClientCursor based on it.
+ * and stores a ClusterClientCursor based on it. 'privileges' contains the required privileges for
+ * the command, to be used by GetMore for auth checks.
*/
StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx,
const NamespaceString& requestedNss,
const ShardId& shardId,
const Shard::CommandResponse& commandResponse,
+ PrivilegeVector privileges,
TailableModeEnum tailableMode);
} // namespace mongo
diff --git a/src/mongo/s/query/store_possible_cursor_test.cpp b/src/mongo/s/query/store_possible_cursor_test.cpp
index 83eef02a50e..d0081d17d77 100644
--- a/src/mongo/s/query/store_possible_cursor_test.cpp
+++ b/src/mongo/s/query/store_possible_cursor_test.cpp
@@ -79,7 +79,8 @@ TEST_F(StorePossibleCursorTest, ReturnsValidCursorResponse) {
cursorResponse.toBSON(CursorResponse::ResponseType::InitialResponse),
nss,
nullptr, // TaskExecutor
- getManager());
+ getManager(),
+ PrivilegeVector());
ASSERT_OK(outgoingCursorResponse.getStatus());
auto parsedOutgoingResponse = CursorResponse::parseFromBSON(outgoingCursorResponse.getValue());
@@ -99,7 +100,8 @@ TEST_F(StorePossibleCursorTest, FailsGracefullyOnBadCursorResponseDocument) {
fromjson("{ok: 1, cursor: {}}"),
nss,
nullptr, // TaskExecutor
- getManager());
+ getManager(),
+ PrivilegeVector());
ASSERT_NOT_OK(outgoingCursorResponse.getStatus());
ASSERT_EQ(ErrorCodes::TypeMismatch, outgoingCursorResponse.getStatus());
}
@@ -115,7 +117,8 @@ TEST_F(StorePossibleCursorTest, PassesUpCommandResultIfItDoesNotDescribeACursor)
notACursorObj,
nss,
nullptr, // TaskExecutor
- getManager());
+ getManager(),
+ PrivilegeVector());
ASSERT_OK(outgoingCursorResponse.getStatus());
ASSERT_BSONOBJ_EQ(notACursorObj, outgoingCursorResponse.getValue());
}