summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Wahlin <james@mongodb.com>2017-07-03 15:33:36 -0400
committerJames Wahlin <james@mongodb.com>2017-07-25 12:24:49 -0400
commit5dcaad5f137eebc1915c0fc7b5078da4aa86f915 (patch)
tree3994b41708bce7cf5cbc5b7c9ba422db77f9bfb3
parent079763d2cd06776edf81f3ecf6c32ab66d1742ec (diff)
downloadmongo-5dcaad5f137eebc1915c0fc7b5078da4aa86f915.tar.gz
SERVER-29371 DocumentSource classes should provide auth requirements
-rw-r--r--jstests/auth/getMore.js3
-rw-r--r--jstests/core/commands_that_do_not_write_do_not_accept_wc.js2
-rw-r--r--jstests/sharding/views.js5
-rw-r--r--src/mongo/db/auth/SConscript4
-rw-r--r--src/mongo/db/auth/authorization_session.cpp310
-rw-r--r--src/mongo/db/auth/authorization_session.h14
-rw-r--r--src/mongo/db/auth/authorization_session_test.cpp121
-rw-r--r--src/mongo/db/auth/privilege.cpp7
-rw-r--r--src/mongo/db/auth/privilege.h3
-rw-r--r--src/mongo/db/pipeline/SConscript12
-rw-r--r--src/mongo/db/pipeline/aggregation_request.cpp15
-rw-r--r--src/mongo/db/pipeline/aggregation_request_test.cpp7
-rw-r--r--src/mongo/db/pipeline/document_source_change_notification.h5
-rw-r--r--src/mongo/db/pipeline/document_source_coll_stats.h15
-rw-r--r--src/mongo/db/pipeline/document_source_current_op.cpp33
-rw-r--r--src/mongo/db/pipeline/document_source_current_op.h30
-rw-r--r--src/mongo/db/pipeline/document_source_facet.cpp16
-rw-r--r--src/mongo/db/pipeline/document_source_facet.h12
-rw-r--r--src/mongo/db/pipeline/document_source_graph_lookup.cpp7
-rw-r--r--src/mongo/db/pipeline/document_source_index_stats.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_index_stats.h25
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.cpp25
-rw-r--r--src/mongo/db/pipeline/document_source_lookup.h40
-rw-r--r--src/mongo/db/pipeline/document_source_lookup_test.cpp3
-rw-r--r--src/mongo/db/pipeline/document_source_out.cpp15
-rw-r--r--src/mongo/db/pipeline/lite_parsed_document_source.h33
-rw-r--r--src/mongo/db/pipeline/lite_parsed_pipeline.h13
-rw-r--r--src/mongo/shell/explainable.js11
28 files changed, 431 insertions, 357 deletions
diff --git a/jstests/auth/getMore.js b/jstests/auth/getMore.js
index d8795278cbc..8e59070391d 100644
--- a/jstests/auth/getMore.js
+++ b/jstests/auth/getMore.js
@@ -145,7 +145,8 @@
adminDB.logout();
assert.eq(1, testDB.auth("Alice", "pwd"));
assert.commandFailedWithCode(
- testDB.runCommand({aggregate: "foo", pipeline: [{$match: {_id: 0}}, {$out: "out"}]}),
+ 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"}));
diff --git a/jstests/core/commands_that_do_not_write_do_not_accept_wc.js b/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
index ef5f8762a42..4b882c95fc0 100644
--- a/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
+++ b/jstests/core/commands_that_do_not_write_do_not_accept_wc.js
@@ -12,7 +12,7 @@
commands.push({count: collName, query: {type: 'oak'}});
- commands.push({aggregate: collName, pipeline: [{$sort: {type: 1}}]});
+ commands.push({aggregate: collName, pipeline: [{$sort: {type: 1}}], cursor: {}});
commands.push({
mapReduce: collName,
diff --git a/jstests/sharding/views.js b/jstests/sharding/views.js
index 09196702ed0..87a2938afac 100644
--- a/jstests/sharding/views.js
+++ b/jstests/sharding/views.js
@@ -84,11 +84,12 @@
db.runCommand({aggregate: "view", pipeline: [{$match: {a: {$lte: 8}}}], explain: true});
verifyExplainResult(result, "queryPlanner");
- result = db.runCommand({explain: {aggregate: "view", pipeline: [{$match: {a: {$lte: 8}}}]}});
+ result = db.runCommand(
+ {explain: {aggregate: "view", pipeline: [{$match: {a: {$lte: 8}}}], cursor: {}}});
verifyExplainResult(result, "allPlansExecution");
for (let verbosity of explainVerbosities) {
result = db.runCommand({
- explain: {aggregate: "view", pipeline: [{$match: {a: {$lte: 8}}}]},
+ explain: {aggregate: "view", pipeline: [{$match: {a: {$lte: 8}}}], cursor: {}},
verbosity: verbosity
});
verifyExplainResult(result, verbosity);
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index 07b685afa64..396c0b4b24b 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -81,7 +81,9 @@ env.Library(
'$BUILD_DIR/mongo/crypto/scramauth',
'$BUILD_DIR/mongo/db/catalog/document_validation',
'$BUILD_DIR/mongo/db/common',
+ '$BUILD_DIR/mongo/db/mongod_options',
'$BUILD_DIR/mongo/db/namespace_string',
+ '$BUILD_DIR/mongo/db/pipeline/lite_parsed_document_source',
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/mongo/db/update/update_driver',
'$BUILD_DIR/mongo/util/md5',
@@ -262,6 +264,8 @@ env.CppUnitTest(
'authmocks',
'saslauth',
'authorization_session_for_test',
+ '$BUILD_DIR/mongo/db/pipeline/document_source',
+ '$BUILD_DIR/mongo/db/service_context_d_test_fixture',
'$BUILD_DIR/mongo/transport/transport_layer_mock',
]
)
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 6c09f5f1f1d..976fa767f3c 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -49,6 +49,8 @@
#include "mongo/db/client.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/db/pipeline/aggregation_request.h"
+#include "mongo/db/pipeline/lite_parsed_pipeline.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
@@ -74,11 +76,13 @@ Status checkAuthForCreateOrModifyView(AuthorizationSession* authzSession,
return Status::OK();
}
- // This check ignores some invalid pipeline specifications. For example, if a user specifies a
- // view definition with an invalid specification like {$lookup: "blah"}, the authorization check
- // will succeed but the pipeline will fail to parse later in Command::run().
+ // 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(
- viewOnNs, BSON("aggregate" << viewOnNs.coll() << "pipeline" << viewPipeline), isMongos);
+ viewOnNs,
+ BSON("aggregate" << viewOnNs.coll() << "pipeline" << viewPipeline << "cursor" << BSONObj()),
+ isMongos);
}
/** Deleter for User*.
@@ -248,245 +252,6 @@ PrivilegeVector AuthorizationSession::getDefaultPrivileges() {
return defaultPrivileges;
}
-Status AuthorizationSession::_addPrivilegesForStage(StringData db,
- BSONObj stageSpec,
- bool bypassDocumentValidation,
- bool isMongos,
- PrivilegeVector* requiredPrivileges) {
- StringData stageName = stageSpec.firstElementFieldName();
- if (stageName == "$out") {
- if (stageSpec.firstElementType() != BSONType::String) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "The '$out' stage must be of type String, is type "
- << stageSpec.firstElementType());
- }
-
- NamespaceString outputNs(db, stageSpec.firstElement().valueStringData());
- if (!outputNs.isValid()) {
- return Status(ErrorCodes::InvalidNamespace,
- mongoutils::str::stream() << "Invalid $out target namespace, "
- << outputNs.ns());
- }
-
- ActionSet actions;
- actions.addAction(ActionType::remove);
- actions.addAction(ActionType::insert);
- if (bypassDocumentValidation) {
- actions.addAction(ActionType::bypassDocumentValidation);
- }
- Privilege::addPrivilegeToPrivilegeVector(
- requiredPrivileges, Privilege(ResourcePattern::forExactNamespace(outputNs), actions));
- } else if (stageName == "$lookup") {
- if (stageSpec.firstElementType() != BSONType::Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "The '$lookup' stage must be of type Object, is type "
- << stageSpec.firstElementType());
- }
-
- auto fromElem = stageSpec.firstElement().Obj()["from"];
- if (!fromElem) {
- return Status(ErrorCodes::FailedToParse,
- "$lookup argument 'from' field must be specified");
- }
-
- if (fromElem.type() != BSONType::String) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "$lookup argument '" << fromElem
- << "' must be a string, is type "
- << fromElem.type());
- }
-
- NamespaceString fromNs(db, fromElem.valueStringData());
- if (!fromNs.isValid()) {
- return Status(ErrorCodes::InvalidNamespace,
- mongoutils::str::stream() << "Invalid 'from' namespace, " << fromNs.ns());
- }
-
- Privilege::addPrivilegeToPrivilegeVector(
- requiredPrivileges,
- Privilege(ResourcePattern::forExactNamespace(fromNs), ActionType::find));
-
- auto pipelineElem = stageSpec.firstElement().Obj()["pipeline"];
- if (pipelineElem) {
- auto status = _addPrivilegesForPipeline(
- fromNs, pipelineElem, bypassDocumentValidation, isMongos, requiredPrivileges);
- if (!status.isOK()) {
- return status;
- }
- }
- } else if (stageName == "$graphLookup") {
- if (stageSpec.firstElementType() != BSONType::Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream()
- << "The '$graphLookup' stage must be of type Object, is type "
- << stageSpec.firstElementType());
- }
-
- auto fromElem = stageSpec.firstElement().Obj()["from"];
- if (!fromElem) {
- return Status(ErrorCodes::FailedToParse,
- "$graphLookup argument 'from' field must be specified");
- }
-
- if (fromElem.type() != BSONType::String) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "$graphLookup argument '" << fromElem
- << "' must be a string, is type "
- << fromElem.type());
- }
-
- NamespaceString fromNs(db, fromElem.valueStringData());
- Privilege::addPrivilegeToPrivilegeVector(
- requiredPrivileges,
- Privilege(ResourcePattern::forExactNamespace(fromNs), ActionType::find));
- } else if (stageName == "$facet") {
- if (stageSpec.firstElementType() != BSONType::Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "The '$facet' stage must be of type Object, is type "
- << stageSpec.firstElementType());
- }
-
- for (auto&& subPipeline : stageSpec.firstElement().embeddedObject()) {
- if (subPipeline.type() != BSONType::Array) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "The '$facet' field '" << subPipeline.fieldName()
- << "' is expected to be of type Array, is type "
- << subPipeline.type());
- }
-
- for (auto&& subPipeStageSpec : subPipeline.embeddedObject()) {
- if (subPipeStageSpec.type() != BSONType::Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "argument '" << subPipeStageSpec
- << "' must be an Object, is type "
- << subPipeStageSpec.type());
- }
-
- auto status = _addPrivilegesForStage(db,
- subPipeStageSpec.embeddedObject(),
- bypassDocumentValidation,
- isMongos,
- requiredPrivileges);
- if (!status.isOK()) {
- return status;
- }
- }
- }
- }
-
- return Status::OK();
-}
-
-Status AuthorizationSession::_addPrivilegesForPipeline(const NamespaceString& nss,
- const BSONElement& pipelineElem,
- bool bypassDocumentValidation,
- bool isMongos,
- PrivilegeVector* requiredPrivileges) {
- if (pipelineElem.type() != BSONType::Array) {
- return Status(ErrorCodes::TypeMismatch, "'pipeline' must be specified as an array");
- }
-
- BSONObj pipeline = pipelineElem.embeddedObject();
- if (pipeline.isEmpty()) {
- // The pipeline is empty, so we require only the find action.
- Privilege::addPrivilegeToPrivilegeVector(
- requiredPrivileges,
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find));
- } else {
- if (pipeline.firstElementType() != BSONType::Object) {
- // The pipeline contains something that's not an object.
- return Status(ErrorCodes::TypeMismatch,
- "'pipeline' cannot contain non-object elements");
- }
-
- // We treat the first stage in the pipeline specially, as some aggregation stages that are
- // valid initial sources have different auth requirements.
- Privilege firstStagePrivilege;
- BSONObj firstPipelineStage = pipeline.firstElement().embeddedObject();
- BSONElement firstStageSpec = firstPipelineStage.firstElement();
- if (str::equals("$indexStats", firstStageSpec.fieldName())) {
- firstStagePrivilege =
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::indexStats);
- } else if (str::equals("$collStats", firstStageSpec.fieldName())) {
- firstStagePrivilege =
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::collStats);
- } else if (str::equals("$currentOp", firstStageSpec.fieldName())) {
- // Need to check the value of allUsers; if true then inprog privilege is required.
- // {$currentOp: {idleConnections: <boolean|false>, allUsers: <boolean|false>}}
- if (firstStageSpec.type() != BSONType::Object) {
- return Status(
- ErrorCodes::TypeMismatch,
- str::stream()
- << "$currentOp options must be specified in an object, but found: "
- << typeName(firstStageSpec.type()));
- }
-
- bool allUsers = false;
-
- // Check the spec for all fields named 'allUsers'. If any of them are 'true', we require
- // the 'inprog' privilege. This avoids the possibility that a spec with multiple
- // allUsers fields might allow an unauthorized user to view all operations.
- for (auto&& elem : firstStageSpec.embeddedObject()) {
- if (elem.fieldNameStringData() == "allUsers"_sd) {
- if (elem.type() != BSONType::Bool) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream()
- << "The 'allUsers' parameter of the $currentOp stage "
- "must be a boolean value, but found: "
- << typeName(elem.type()));
- } else if (elem.Bool()) {
- allUsers = true;
- break;
- }
- }
- }
-
- // In a sharded cluster, we always need the inprog privilege to run $currentOp.
- if (isMongos || allUsers) {
- firstStagePrivilege =
- Privilege(ResourcePattern::forClusterResource(), ActionType::inprog);
- } else if (!getAuthenticatedUserNames().more()) {
- // This connection is not authenticated, so we should return an error even though
- // there are no privilege requirements when allUsers is false.
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
- } else {
- // If no source requiring an alternative permission scheme is specified then default to
- // requiring find() privileges on the given namespace.
- firstStagePrivilege =
- Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find);
- }
-
- // Exit early if not authorized for the pipline's input data source. This will prevent a
- // malicious user, who doesn't have access to the initial document source, from consuming
- // server resources needed to parse a potentially large pipeline.
- if (!isAuthorizedForPrivilege(firstStagePrivilege)) {
- return Status(ErrorCodes::Unauthorized, "unauthorized");
- }
-
- // Add additional required privileges for each stage in the pipeline.
- for (auto&& stageElem : pipeline) {
- if (stageElem.type() != BSONType::Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "argument '" << stageElem
- << "' must be an Object, is type "
- << stageElem.type());
- }
-
- auto status = _addPrivilegesForStage(nss.db(),
- stageElem.embeddedObject(),
- bypassDocumentValidation,
- isMongos,
- requiredPrivileges);
- if (!status.isOK()) {
- return status;
- }
- }
- }
-
- return Status::OK();
-}
-
Status AuthorizationSession::checkAuthForAggregate(const NamespaceString& nss,
const BSONObj& cmdObj,
bool isMongos) {
@@ -501,20 +266,59 @@ Status AuthorizationSession::checkAuthForAggregate(const NamespaceString& nss,
return Status::OK();
}
- PrivilegeVector privileges;
- auto status = _addPrivilegesForPipeline(nss,
- cmdObj["pipeline"],
- shouldBypassDocumentValidationForCommand(cmdObj),
- isMongos,
- &privileges);
- if (!status.isOK()) {
- return status;
+ // We require at least one authenticated user when running aggregate with auth enabled.
+ if (!getAuthenticatedUserNames().more()) {
+ return Status(ErrorCodes::Unauthorized, "unauthorized");
+ }
+
+ auto statusWithAggRequest = AggregationRequest::parseFromBSON(nss, cmdObj);
+ if (!statusWithAggRequest.isOK()) {
+ return statusWithAggRequest.getStatus();
}
+ AggregationRequest aggRequest = std::move(statusWithAggRequest.getValue());
+
+ const auto& pipeline = aggRequest.getPipeline();
+
+ // 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");
+ }
- if (isAuthorizedForPrivileges(privileges))
return Status::OK();
+ }
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ // 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");
+ }
+
+ // 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");
+ }
+ }
+
+ return Status::OK();
}
Status AuthorizationSession::checkAuthForFind(const NamespaceString& ns, bool hasTerm) {
diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h
index 0161523a79b..65588ab0708 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -312,20 +312,6 @@ private:
// lock on the admin database (to update out-of-date user privilege information).
bool _isAuthorizedForPrivilege(const Privilege& privilege);
- // Helper for recursively checking for privileges in an aggregation pipeline.
- Status _addPrivilegesForPipeline(const NamespaceString& nss,
- const BSONElement& pipelineElem,
- bool bypassDocumentValidation,
- bool isMongos,
- PrivilegeVector* requiredPrivileges);
-
- // Helper for recursively checking for privileges in an aggregation stage.
- Status _addPrivilegesForStage(StringData db,
- BSONObj stageSpec,
- bool bypassDocumentValidation,
- bool isMongos,
- PrivilegeVector* requiredPrivileges);
-
std::unique_ptr<AuthzSessionExternalState> _externalState;
// A vector of impersonated UserNames and a vector of those users' RoleNames.
diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp
index d6cd4d686fe..9cdbe4aa730 100644
--- a/src/mongo/db/auth/authorization_session_test.cpp
+++ b/src/mongo/db/auth/authorization_session_test.cpp
@@ -734,6 +734,8 @@ TEST_F(AuthorizationSessionTest, AcquireUserObtainsAndValidatesAuthenticationRes
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineIsNotAnArray) {
+ authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
+
BSONObj cmdObjIntPipeline = BSON("aggregate" << testFooNss.coll() << "pipeline" << 7);
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjIntPipeline, false));
@@ -748,6 +750,8 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineIsNotAnArra
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineFirstStageIsNotAnObject) {
+ authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
+
BSONObj cmdObjFirstStageInt =
BSON("aggregate" << testFooNss.coll() << "pipeline" << BSON_ARRAY(7));
ASSERT_EQ(ErrorCodes::TypeMismatch,
@@ -760,7 +764,8 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineFirstStageI
}
TEST_F(AuthorizationSessionTest, CannotAggregateEmptyPipelineWithoutFindAction) {
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray());
+ BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray() << "cursor"
+ << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -768,7 +773,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateEmptyPipelineWithoutFindAction)
TEST_F(AuthorizationSessionTest, CanAggregateEmptyPipelineWithFindAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray());
+ BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray() << "cursor"
+ << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -778,25 +784,28 @@ TEST_F(AuthorizationSessionTest, CannotAggregateWithoutFindActionIfFirstStageNot
BSONArray pipeline = BSON_ARRAY(BSON("$limit" << 1) << BSON("$collStats" << BSONObj())
<< BSON("$indexStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
-TEST_F(AuthorizationSessionTest, CanAggregateWithFindActionIfFirstStageNotIndexOrCollStats) {
+TEST_F(AuthorizationSessionTest, CannotAggregateWithFindActionIfPipelineContainsIndexOrCollStats) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
-
BSONArray pipeline = BSON_ARRAY(BSON("$limit" << 1) << BSON("$collStats" << BSONObj())
<< BSON("$indexStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
- ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
+ ASSERT_EQ(ErrorCodes::Unauthorized,
+ authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -805,7 +814,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::collStats}));
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -813,7 +823,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsActio
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -822,7 +833,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::indexStats}));
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -830,7 +842,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersFalseWithoutInprog
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -838,14 +851,16 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseWithoutInp
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthenticatedOnMongoD) {
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
@@ -853,7 +868,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthe
TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthenticatedOnMongoS) {
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
@@ -863,7 +879,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -872,7 +889,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
}
@@ -882,7 +900,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi
Privilege(ResourcePattern::forClusterResource(), {ActionType::inprog}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -891,7 +910,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi
Privilege(ResourcePattern::forClusterResource(), {ActionType::inprog}));
BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
}
@@ -900,7 +920,8 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon
BSONArray pipeline =
BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -910,7 +931,8 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon
BSONArray pipeline =
BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, true));
}
@@ -920,9 +942,11 @@ TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotVa
BSONArray pipeline = BSON_ARRAY(BSON("$out"
<< ""));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
- ASSERT_EQ(ErrorCodes::InvalidNamespace,
- authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
+ ASSERT_THROWS_CODE(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false).ignore(),
+ UserException,
+ ErrorCodes::InvalidNamespace);
}
TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTargetNamespace) {
@@ -930,7 +954,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTarge
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
@@ -953,12 +978,15 @@ TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNames
Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
BSONObj cmdObjNoBypassDocumentValidation = BSON(
"aggregate" << testFooNss.coll() << "pipeline" << pipeline << "bypassDocumentValidation"
- << false);
+ << false
+ << "cursor"
+ << BSONObj());
ASSERT_OK(
authzSession->checkAuthForAggregate(testFooNss, cmdObjNoBypassDocumentValidation, false));
}
@@ -970,9 +998,10 @@ TEST_F(AuthorizationSessionTest,
Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline
- << "bypassDocumentValidation"
- << true);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj()
+ << "bypassDocumentValidation"
+ << true);
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -986,9 +1015,10 @@ TEST_F(AuthorizationSessionTest,
{ActionType::insert, ActionType::remove, ActionType::bypassDocumentValidation})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline
- << "bypassDocumentValidation"
- << true);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj()
+ << "bypassDocumentValidation"
+ << true);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -996,7 +1026,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespa
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1006,7 +1037,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) {
Privilege(testBarCollResource, {ActionType::find})});
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1018,7 +1050,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnNestedJoinedN
BSONArray nestedPipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testQuxNss.coll())));
BSONArray pipeline = BSON_ARRAY(
BSON("$lookup" << BSON("from" << testBarNss.coll() << "pipeline" << nestedPipeline)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1031,7 +1064,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnNestedJoinedNamespa
BSONArray nestedPipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testQuxNss.coll())));
BSONArray pipeline = BSON_ARRAY(
BSON("$lookup" << BSON("from" << testBarNss.coll() << "pipeline" << nestedPipeline)));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1071,6 +1105,7 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateWithDeeplyNestedLookup) {
BSONArrayBuilder pipelineBuilder(cmdBuilder.subarrayStart("pipeline"));
addNestedPipeline(&pipelineBuilder, maxLookupDepth);
pipelineBuilder.doneFast();
+ cmdBuilder << "cursor" << BSONObj();
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdBuilder.obj(), false));
}
@@ -1080,7 +1115,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateGraphLookupWithoutFindOnJoinedNa
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1090,7 +1126,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespac
Privilege(testBarCollResource, {ActionType::find})});
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
@@ -1102,7 +1139,8 @@ TEST_F(AuthorizationSessionTest,
BSONArray pipeline =
BSON_ARRAY(fromjson("{$facet: {lookup: [{$lookup: {from: 'bar'}}], graphLookup: "
"[{$graphLookup: {from: 'qux'}}]}}"));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_EQ(ErrorCodes::Unauthorized,
authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
@@ -1128,7 +1166,8 @@ TEST_F(AuthorizationSessionTest,
BSONArray pipeline =
BSON_ARRAY(fromjson("{$facet: {lookup: [{$lookup: {from: 'bar'}}], graphLookup: "
"[{$graphLookup: {from: 'qux'}}]}}"));
- BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
+ BSONObj cmdObj =
+ BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline << "cursor" << BSONObj());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj, false));
}
diff --git a/src/mongo/db/auth/privilege.cpp b/src/mongo/db/auth/privilege.cpp
index 6e33bb54082..93c75cce9d8 100644
--- a/src/mongo/db/auth/privilege.cpp
+++ b/src/mongo/db/auth/privilege.cpp
@@ -45,6 +45,13 @@ void Privilege::addPrivilegeToPrivilegeVector(PrivilegeVector* privileges,
privileges->push_back(privilegeToAdd);
}
+void Privilege::addPrivilegesToPrivilegeVector(PrivilegeVector* privileges,
+ const PrivilegeVector& privilegesToAdd) {
+ for (auto&& priv : privilegesToAdd) {
+ addPrivilegeToPrivilegeVector(privileges, priv);
+ }
+}
+
Privilege::Privilege(const ResourcePattern& resource, const ActionType& action)
: _resource(resource) {
_actions.addAction(action);
diff --git a/src/mongo/db/auth/privilege.h b/src/mongo/db/auth/privilege.h
index c946123947f..43f5fb48d9c 100644
--- a/src/mongo/db/auth/privilege.h
+++ b/src/mongo/db/auth/privilege.h
@@ -52,6 +52,9 @@ public:
static void addPrivilegeToPrivilegeVector(PrivilegeVector* privileges,
const Privilege& privilegeToAdd);
+ static void addPrivilegesToPrivilegeVector(PrivilegeVector* privileges,
+ const PrivilegeVector& privilegesToAdd);
+
Privilege(){};
Privilege(const ResourcePattern& resource, const ActionType& action);
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 85c78e5a0c9..0d67ccbb2df 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -254,7 +254,6 @@ docSourceEnv.Library(
'document_source_sort.cpp',
'document_source_sort_by_count.cpp',
'document_source_unwind.cpp',
- 'lite_parsed_document_source.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/client/clientdriver',
@@ -262,6 +261,7 @@ docSourceEnv.Library(
'$BUILD_DIR/mongo/db/index/key_generator',
'$BUILD_DIR/mongo/db/matcher/expression_algo',
'$BUILD_DIR/mongo/db/matcher/expressions',
+ '$BUILD_DIR/mongo/db/pipeline/lite_parsed_document_source',
'$BUILD_DIR/mongo/db/service_context',
'$BUILD_DIR/mongo/db/stats/top',
'$BUILD_DIR/mongo/db/storage/encryption_hooks',
@@ -278,6 +278,16 @@ docSourceEnv.Library(
)
env.Library(
+ target='lite_parsed_document_source',
+ source=[
+ 'lite_parsed_document_source.cpp',
+ ],
+ LIBDEPS=[
+ 'aggregation_request',
+ ]
+)
+
+env.Library(
target='pipeline',
source=[
'pipeline.cpp',
diff --git a/src/mongo/db/pipeline/aggregation_request.cpp b/src/mongo/db/pipeline/aggregation_request.cpp
index 844907bd89a..8947c6f9a03 100644
--- a/src/mongo/db/pipeline/aggregation_request.cpp
+++ b/src/mongo/db/pipeline/aggregation_request.cpp
@@ -216,10 +216,14 @@ StatusWith<AggregationRequest> AggregationRequest::parseFromBSON(
request.setExplain(explainVerbosity);
}
- if (!hasCursorElem && !request.getExplain()) {
+ // 'hasExplainElem' implies an aggregate command-level explain option, which does not require
+ // a cursor argument.
+ if (!hasCursorElem && !hasExplainElem) {
return {ErrorCodes::FailedToParse,
- str::stream() << "The '" << kCursorName
- << "' option is required, except for aggregation explain"};
+ str::stream()
+ << "The '"
+ << kCursorName
+ << "' option is required, except for aggregate with the explain argument"};
}
if (request.getExplain() && !request.getReadConcern().isEmpty()) {
@@ -277,8 +281,9 @@ Document AggregationRequest::serializeToCommandObj() const {
_bypassDocumentValidation ? Value(true) : Value()},
// Only serialize a collation if one was specified.
{kCollationName, _collation.isEmpty() ? Value() : Value(_collation)},
- // Only serialize batchSize when explain is false.
- {kCursorName, _explainMode ? Value() : Value(Document{{kBatchSizeName, _batchSize}})},
+ // Only serialize batchSize if not an explain, otherwise serialize an empty cursor object.
+ {kCursorName,
+ _explainMode ? Value(Document()) : Value(Document{{kBatchSizeName, _batchSize}})},
// Only serialize a hint if one was specified.
{kHintName, _hint.isEmpty() ? Value() : Value(_hint)},
// Only serialize a comment if one was specified.
diff --git a/src/mongo/db/pipeline/aggregation_request_test.cpp b/src/mongo/db/pipeline/aggregation_request_test.cpp
index 0b1330009da..109bb6083ef 100644
--- a/src/mongo/db/pipeline/aggregation_request_test.cpp
+++ b/src/mongo/db/pipeline/aggregation_request_test.cpp
@@ -98,7 +98,7 @@ TEST(AggregationRequestTest, ShouldParseExplicitExplainFalseWithCursorOption) {
TEST(AggregationRequestTest, ShouldParseWithSeparateQueryPlannerExplainModeArg) {
NamespaceString nss("a.collection");
- const BSONObj inputBson = fromjson("{pipeline: []}");
+ const BSONObj inputBson = fromjson("{pipeline: [], cursor: {}}");
auto request = unittest::assertGet(AggregationRequest::parseFromBSON(
nss, inputBson, ExplainOptions::Verbosity::kQueryPlanner));
ASSERT_TRUE(request.getExplain());
@@ -236,7 +236,7 @@ TEST(AggregationRequestTest, ShouldAcceptHintAsString) {
<< "a_1"));
}
-TEST(AggregationRequestTest, ShouldNotSerializeBatchSizeOrExplainWhenExplainSet) {
+TEST(AggregationRequestTest, ShouldNotSerializeBatchSizeWhenExplainSet) {
NamespaceString nss("a.collection");
AggregationRequest request(nss, {});
request.setBatchSize(10);
@@ -244,7 +244,8 @@ TEST(AggregationRequestTest, ShouldNotSerializeBatchSizeOrExplainWhenExplainSet)
auto expectedSerialization =
Document{{AggregationRequest::kCommandName, nss.coll()},
- {AggregationRequest::kPipelineName, Value(std::vector<Value>{})}};
+ {AggregationRequest::kPipelineName, Value(std::vector<Value>{})},
+ {AggregationRequest::kCursorName, Value(Document())}};
ASSERT_DOCUMENT_EQ(request.serializeToCommandObj(), expectedSerialization);
}
diff --git a/src/mongo/db/pipeline/document_source_change_notification.h b/src/mongo/db/pipeline/document_source_change_notification.h
index c6748ab3781..e5a1186f04e 100644
--- a/src/mongo/db/pipeline/document_source_change_notification.h
+++ b/src/mongo/db/pipeline/document_source_change_notification.h
@@ -53,6 +53,11 @@ public:
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return stdx::unordered_set<NamespaceString>();
}
+
+ // TODO SERVER-29138: Add required privileges.
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return {};
+ }
};
class Transformation : public DocumentSourceSingleDocumentTransformation::TransformerInterface {
diff --git a/src/mongo/db/pipeline/document_source_coll_stats.h b/src/mongo/db/pipeline/document_source_coll_stats.h
index ad8673643e6..2506c1be823 100644
--- a/src/mongo/db/pipeline/document_source_coll_stats.h
+++ b/src/mongo/db/pipeline/document_source_coll_stats.h
@@ -42,16 +42,29 @@ public:
public:
static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request,
const BSONElement& spec) {
- return stdx::make_unique<LiteParsed>();
+ return stdx::make_unique<LiteParsed>(request.getNamespaceString());
}
+ explicit LiteParsed(NamespaceString nss) : _nss(std::move(nss)) {}
+
bool isCollStats() const final {
return true;
}
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return {Privilege(ResourcePattern::forExactNamespace(_nss), ActionType::collStats)};
+ }
+
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return stdx::unordered_set<NamespaceString>();
}
+
+ bool isInitialSource() const final {
+ return true;
+ }
+
+ private:
+ const NamespaceString _nss;
};
DocumentSourceCollStats(const boost::intrusive_ptr<ExpressionContext>& pExpCtx)
diff --git a/src/mongo/db/pipeline/document_source_current_op.cpp b/src/mongo/db/pipeline/document_source_current_op.cpp
index 6a37192f8ad..b49d7de702c 100644
--- a/src/mongo/db/pipeline/document_source_current_op.cpp
+++ b/src/mongo/db/pipeline/document_source_current_op.cpp
@@ -48,9 +48,40 @@ const StringData kShardFieldName = "shard"_sd;
using boost::intrusive_ptr;
REGISTER_DOCUMENT_SOURCE(currentOp,
- LiteParsedDocumentSourceDefault::parse,
+ DocumentSourceCurrentOp::LiteParsed::parse,
DocumentSourceCurrentOp::createFromBson);
+std::unique_ptr<DocumentSourceCurrentOp::LiteParsed> DocumentSourceCurrentOp::LiteParsed::parse(
+ const AggregationRequest& request, const BSONElement& spec) {
+ // Need to check the value of allUsers; if true then inprog privilege is required.
+ if (spec.type() != BSONType::Object) {
+ uasserted(ErrorCodes::TypeMismatch,
+ str::stream() << "$currentOp options must be specified in an object, but found: "
+ << typeName(spec.type()));
+ }
+
+ bool allUsers = false;
+
+ // Check the spec for all fields named 'allUsers'. If any of them are 'true', we require
+ // the 'inprog' privilege. This avoids the possibility that a spec with multiple
+ // allUsers fields might allow an unauthorized user to view all operations.
+ for (auto&& elem : spec.embeddedObject()) {
+ if (elem.fieldNameStringData() == "allUsers"_sd) {
+ if (elem.type() != BSONType::Bool) {
+ uasserted(ErrorCodes::TypeMismatch,
+ str::stream() << "The 'allUsers' parameter of the $currentOp stage "
+ "must be a boolean value, but found: "
+ << typeName(elem.type()));
+ }
+
+ allUsers = allUsers || elem.boolean();
+ }
+ }
+
+ return stdx::make_unique<DocumentSourceCurrentOp::LiteParsed>(allUsers);
+}
+
+
const char* DocumentSourceCurrentOp::getSourceName() const {
return "$currentOp";
}
diff --git a/src/mongo/db/pipeline/document_source_current_op.h b/src/mongo/db/pipeline/document_source_current_op.h
index 24ef1ba964d..4526134c13f 100644
--- a/src/mongo/db/pipeline/document_source_current_op.h
+++ b/src/mongo/db/pipeline/document_source_current_op.h
@@ -34,6 +34,36 @@ namespace mongo {
class DocumentSourceCurrentOp final : public DocumentSourceNeedsMongod {
public:
+ class LiteParsed final : public LiteParsedDocumentSource {
+ public:
+ static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request,
+ const BSONElement& spec);
+
+ explicit LiteParsed(bool allUsers) : _allUsers(allUsers) {}
+
+ stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
+ return stdx::unordered_set<NamespaceString>();
+ }
+
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ PrivilegeVector privileges;
+
+ // In a sharded cluster, we always need the inprog privilege to run $currentOp.
+ if (isMongos || _allUsers) {
+ privileges.push_back({ResourcePattern::forClusterResource(), ActionType::inprog});
+ }
+
+ return privileges;
+ }
+
+ bool isInitialSource() const final {
+ return true;
+ }
+
+ private:
+ const bool _allUsers;
+ };
+
using TruncationMode = MongodInterface::CurrentOpTruncateMode;
using ConnMode = MongodInterface::CurrentOpConnectionsMode;
using UserMode = MongodInterface::CurrentOpUserMode;
diff --git a/src/mongo/db/pipeline/document_source_facet.cpp b/src/mongo/db/pipeline/document_source_facet.cpp
index 222d78e69c3..992f3daac11 100644
--- a/src/mongo/db/pipeline/document_source_facet.cpp
+++ b/src/mongo/db/pipeline/document_source_facet.cpp
@@ -118,12 +118,24 @@ vector<pair<string, vector<BSONObj>>> extractRawPipelines(const BSONElement& ele
std::unique_ptr<DocumentSourceFacet::LiteParsed> DocumentSourceFacet::LiteParsed::parse(
const AggregationRequest& request, const BSONElement& spec) {
std::vector<LiteParsedPipeline> liteParsedPipelines;
+
for (auto&& rawPipeline : extractRawPipelines(spec)) {
liteParsedPipelines.emplace_back(
AggregationRequest(request.getNamespaceString(), rawPipeline.second));
}
- return std::unique_ptr<DocumentSourceFacet::LiteParsed>(
- new DocumentSourceFacet::LiteParsed(std::move(liteParsedPipelines)));
+
+ PrivilegeVector requiredPrivileges;
+ for (auto&& pipeline : liteParsedPipelines) {
+
+ // A correct isMongos flag is only required for DocumentSourceCurrentOp which is disallowed
+ // in $facet pipelines.
+ const bool unusedIsMongosFlag = false;
+ Privilege::addPrivilegesToPrivilegeVector(&requiredPrivileges,
+ pipeline.requiredPrivileges(unusedIsMongosFlag));
+ }
+
+ return stdx::make_unique<DocumentSourceFacet::LiteParsed>(std::move(liteParsedPipelines),
+ std::move(requiredPrivileges));
}
stdx::unordered_set<NamespaceString> DocumentSourceFacet::LiteParsed::getInvolvedNamespaces()
diff --git a/src/mongo/db/pipeline/document_source_facet.h b/src/mongo/db/pipeline/document_source_facet.h
index 37c2fc5cb8f..f06f73c7b27 100644
--- a/src/mongo/db/pipeline/document_source_facet.h
+++ b/src/mongo/db/pipeline/document_source_facet.h
@@ -71,13 +71,19 @@ public:
static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request,
const BSONElement& spec);
+ LiteParsed(std::vector<LiteParsedPipeline> liteParsedPipelines, PrivilegeVector privileges)
+ : _liteParsedPipelines(std::move(liteParsedPipelines)),
+ _requiredPrivileges(std::move(privileges)) {}
+
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return _requiredPrivileges;
+ }
+
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final;
private:
- LiteParsed(std::vector<LiteParsedPipeline> liteParsedPipelines)
- : _liteParsedPipelines(std::move(liteParsedPipelines)) {}
-
const std::vector<LiteParsedPipeline> _liteParsedPipelines;
+ const PrivilegeVector _requiredPrivileges;
};
static boost::intrusive_ptr<DocumentSource> createFromBson(
diff --git a/src/mongo/db/pipeline/document_source_graph_lookup.cpp b/src/mongo/db/pipeline/document_source_graph_lookup.cpp
index 19ce53b3769..bac942e1f31 100644
--- a/src/mongo/db/pipeline/document_source_graph_lookup.cpp
+++ b/src/mongo/db/pipeline/document_source_graph_lookup.cpp
@@ -71,7 +71,12 @@ std::unique_ptr<LiteParsedDocumentSourceForeignCollections> DocumentSourceGraphL
uassert(ErrorCodes::InvalidNamespace,
str::stream() << "invalid $graphLookup namespace: " << nss.ns(),
nss.isValid());
- return stdx::make_unique<LiteParsedDocumentSourceForeignCollections>(std::move(nss));
+
+ PrivilegeVector privileges{
+ Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find)};
+
+ return stdx::make_unique<LiteParsedDocumentSourceForeignCollections>(std::move(nss),
+ std::move(privileges));
}
REGISTER_DOCUMENT_SOURCE(graphLookup,
diff --git a/src/mongo/db/pipeline/document_source_index_stats.cpp b/src/mongo/db/pipeline/document_source_index_stats.cpp
index e6d5b28171c..786f8f59cf0 100644
--- a/src/mongo/db/pipeline/document_source_index_stats.cpp
+++ b/src/mongo/db/pipeline/document_source_index_stats.cpp
@@ -39,7 +39,7 @@ namespace mongo {
using boost::intrusive_ptr;
REGISTER_DOCUMENT_SOURCE(indexStats,
- LiteParsedDocumentSourceDefault::parse,
+ DocumentSourceIndexStats::LiteParsed::parse,
DocumentSourceIndexStats::createFromBson);
const char* DocumentSourceIndexStats::getSourceName() const {
diff --git a/src/mongo/db/pipeline/document_source_index_stats.h b/src/mongo/db/pipeline/document_source_index_stats.h
index e802e0d7016..7d25aca6c9f 100644
--- a/src/mongo/db/pipeline/document_source_index_stats.h
+++ b/src/mongo/db/pipeline/document_source_index_stats.h
@@ -39,6 +39,31 @@ namespace mongo {
*/
class DocumentSourceIndexStats final : public DocumentSourceNeedsMongod {
public:
+ class LiteParsed final : public LiteParsedDocumentSource {
+ public:
+ static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request,
+ const BSONElement& spec) {
+ return stdx::make_unique<LiteParsed>(request.getNamespaceString());
+ }
+
+ explicit LiteParsed(NamespaceString nss) : _nss(std::move(nss)) {}
+
+ stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
+ return stdx::unordered_set<NamespaceString>();
+ }
+
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return {Privilege(ResourcePattern::forExactNamespace(_nss), ActionType::indexStats)};
+ }
+
+ bool isInitialSource() const final {
+ return true;
+ }
+
+ private:
+ const NamespaceString _nss;
+ };
+
// virtuals from DocumentSource
GetNextResult getNext() final;
const char* getSourceName() const final;
diff --git a/src/mongo/db/pipeline/document_source_lookup.cpp b/src/mongo/db/pipeline/document_source_lookup.cpp
index ec2d37db6de..6fa34fba0df 100644
--- a/src/mongo/db/pipeline/document_source_lookup.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup.cpp
@@ -37,7 +37,6 @@
#include "mongo/db/pipeline/document_path_support.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/lite_parsed_pipeline.h"
#include "mongo/db/pipeline/value.h"
#include "mongo/stdx/memory.h"
@@ -117,7 +116,7 @@ DocumentSourceLookUp::DocumentSourceLookUp(NamespaceString fromNs,
}
}
-std::unique_ptr<LiteParsedDocumentSourceForeignCollections> DocumentSourceLookUp::liteParse(
+std::unique_ptr<DocumentSourceLookUp::LiteParsed> DocumentSourceLookUp::LiteParsed::parse(
const AggregationRequest& request, const BSONElement& spec) {
uassert(ErrorCodes::FailedToParse,
str::stream() << "the $lookup stage specification must be an object, but found "
@@ -134,29 +133,33 @@ std::unique_ptr<LiteParsedDocumentSourceForeignCollections> DocumentSourceLookUp
<< typeName(specObj["from"].type()),
fromElement.type() == BSONType::String);
- NamespaceString nss(request.getNamespaceString().db(), fromElement.valueStringData());
+ NamespaceString fromNss(request.getNamespaceString().db(), fromElement.valueStringData());
uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "invalid $lookup namespace: " << nss.ns(),
- nss.isValid());
+ str::stream() << "invalid $lookup namespace: " << fromNss.ns(),
+ fromNss.isValid());
stdx::unordered_set<NamespaceString> foreignNssSet;
// Recursively lite parse the nested pipeline, if one exists.
auto pipelineElem = specObj["pipeline"];
+ boost::optional<LiteParsedPipeline> liteParsedPipeline;
if (pipelineElem) {
auto pipeline = uassertStatusOK(AggregationRequest::parsePipelineFromBSON(pipelineElem));
- AggregationRequest foreignAggReq(nss, std::move(pipeline));
- LiteParsedPipeline liteParsedPipeline(foreignAggReq);
- auto pipelineInvolvedNamespaces = liteParsedPipeline.getInvolvedNamespaces();
+ AggregationRequest foreignAggReq(fromNss, std::move(pipeline));
+ liteParsedPipeline = LiteParsedPipeline(foreignAggReq);
+
+ auto pipelineInvolvedNamespaces = liteParsedPipeline->getInvolvedNamespaces();
foreignNssSet.insert(pipelineInvolvedNamespaces.begin(), pipelineInvolvedNamespaces.end());
}
- foreignNssSet.insert(std::move(nss));
- return stdx::make_unique<LiteParsedDocumentSourceForeignCollections>(std::move(foreignNssSet));
+ foreignNssSet.insert(fromNss);
+
+ return stdx::make_unique<DocumentSourceLookUp::LiteParsed>(
+ std::move(fromNss), std::move(foreignNssSet), std::move(liteParsedPipeline));
}
REGISTER_DOCUMENT_SOURCE(lookup,
- DocumentSourceLookUp::liteParse,
+ DocumentSourceLookUp::LiteParsed::parse,
DocumentSourceLookUp::createFromBson);
const char* DocumentSourceLookUp::getSourceName() const {
diff --git a/src/mongo/db/pipeline/document_source_lookup.h b/src/mongo/db/pipeline/document_source_lookup.h
index 12965eee2d0..bb4fae8df2e 100644
--- a/src/mongo/db/pipeline/document_source_lookup.h
+++ b/src/mongo/db/pipeline/document_source_lookup.h
@@ -28,10 +28,13 @@
#pragma once
+#include <boost/optional.hpp>
+
#include "mongo/db/pipeline/document_source.h"
#include "mongo/db/pipeline/document_source_match.h"
#include "mongo/db/pipeline/document_source_unwind.h"
#include "mongo/db/pipeline/expression.h"
+#include "mongo/db/pipeline/lite_parsed_pipeline.h"
#include "mongo/db/pipeline/lookup_set_cache.h"
#include "mongo/db/pipeline/value_comparator.h"
@@ -44,8 +47,41 @@ namespace mongo {
class DocumentSourceLookUp final : public DocumentSourceNeedsMongod,
public SplittableDocumentSource {
public:
- static std::unique_ptr<LiteParsedDocumentSourceForeignCollections> liteParse(
- const AggregationRequest& request, const BSONElement& spec);
+ class LiteParsed final : public LiteParsedDocumentSource {
+ public:
+ static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request,
+ const BSONElement& spec);
+
+ LiteParsed(NamespaceString fromNss,
+ stdx::unordered_set<NamespaceString> foreignNssSet,
+ boost::optional<LiteParsedPipeline> liteParsedPipeline)
+ : _fromNss{std::move(fromNss)},
+ _foreignNssSet(std::move(foreignNssSet)),
+ _liteParsedPipeline(std::move(liteParsedPipeline)) {}
+
+ stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
+ return {_foreignNssSet};
+ }
+
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ PrivilegeVector requiredPrivileges;
+ Privilege::addPrivilegeToPrivilegeVector(
+ &requiredPrivileges,
+ Privilege(ResourcePattern::forExactNamespace(_fromNss), ActionType::find));
+
+ if (_liteParsedPipeline) {
+ Privilege::addPrivilegesToPrivilegeVector(
+ &requiredPrivileges, _liteParsedPipeline->requiredPrivileges(isMongos));
+ }
+
+ return requiredPrivileges;
+ }
+
+ private:
+ const NamespaceString _fromNss;
+ const stdx::unordered_set<NamespaceString> _foreignNssSet;
+ const boost::optional<LiteParsedPipeline> _liteParsedPipeline;
+ };
GetNextResult getNext() final;
const char* getSourceName() const final;
diff --git a/src/mongo/db/pipeline/document_source_lookup_test.cpp b/src/mongo/db/pipeline/document_source_lookup_test.cpp
index c9f56aa2295..0ca5f1a475a 100644
--- a/src/mongo/db/pipeline/document_source_lookup_test.cpp
+++ b/src/mongo/db/pipeline/document_source_lookup_test.cpp
@@ -160,7 +160,8 @@ TEST_F(DocumentSourceLookUpTest, LiteParsedDocumentSourceLookupContainsExpectedN
NamespaceString nss("test.test");
std::vector<BSONObj> pipeline;
AggregationRequest aggRequest(nss, pipeline);
- auto liteParsedLookup = DocumentSourceLookUp::liteParse(aggRequest, stageSpec.firstElement());
+ auto liteParsedLookup =
+ DocumentSourceLookUp::LiteParsed::parse(aggRequest, stageSpec.firstElement());
auto namespaceSet = liteParsedLookup->getInvolvedNamespaces();
diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp
index a2e0ceea44b..069257abcb9 100644
--- a/src/mongo/db/pipeline/document_source_out.cpp
+++ b/src/mongo/db/pipeline/document_source_out.cpp
@@ -48,16 +48,25 @@ DocumentSourceOut::~DocumentSourceOut() {
std::unique_ptr<LiteParsedDocumentSourceForeignCollections> DocumentSourceOut::liteParse(
const AggregationRequest& request, const BSONElement& spec) {
- uassert(40325,
+ uassert(ErrorCodes::TypeMismatch,
str::stream() << "$out stage requires a string argument, but found "
<< typeName(spec.type()),
spec.type() == BSONType::String);
NamespaceString targetNss(request.getNamespaceString().db(), spec.valueStringData());
- uassert(40326,
+ uassert(ErrorCodes::InvalidNamespace,
str::stream() << "Invalid $out target namespace, " << targetNss.ns(),
targetNss.isValid());
- return stdx::make_unique<LiteParsedDocumentSourceForeignCollections>(std::move(targetNss));
+
+ ActionSet actions{ActionType::remove, ActionType::insert};
+ if (request.shouldBypassDocumentValidation()) {
+ actions.addAction(ActionType::bypassDocumentValidation);
+ }
+
+ PrivilegeVector privileges{Privilege(ResourcePattern::forExactNamespace(targetNss), actions)};
+
+ return stdx::make_unique<LiteParsedDocumentSourceForeignCollections>(std::move(targetNss),
+ std::move(privileges));
}
REGISTER_DOCUMENT_SOURCE(out, DocumentSourceOut::liteParse, DocumentSourceOut::createFromBson);
diff --git a/src/mongo/db/pipeline/lite_parsed_document_source.h b/src/mongo/db/pipeline/lite_parsed_document_source.h
index dae9ee222ce..489e8bc1aa7 100644
--- a/src/mongo/db/pipeline/lite_parsed_document_source.h
+++ b/src/mongo/db/pipeline/lite_parsed_document_source.h
@@ -32,6 +32,7 @@
#include <memory>
#include <vector>
+#include "mongo/db/auth/privilege.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/aggregation_request.h"
#include "mongo/stdx/functional.h"
@@ -85,6 +86,11 @@ public:
virtual stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const = 0;
/**
+ * Returns a list of the privileges required for this stage.
+ */
+ virtual PrivilegeVector requiredPrivileges(bool isMongos) const = 0;
+
+ /**
* Returns true if this is a $collStats stage.
*/
virtual bool isCollStats() const {
@@ -97,6 +103,13 @@ public:
virtual bool isChangeNotification() const {
return false;
}
+
+ /**
+ * Returns true if this stage does not require an input source.
+ */
+ virtual bool isInitialSource() const {
+ return false;
+ }
};
class LiteParsedDocumentSourceDefault final : public LiteParsedDocumentSource {
@@ -116,6 +129,10 @@ public:
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return stdx::unordered_set<NamespaceString>();
}
+
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return {};
+ }
};
/**
@@ -123,18 +140,24 @@ public:
*/
class LiteParsedDocumentSourceForeignCollections : public LiteParsedDocumentSource {
public:
- explicit LiteParsedDocumentSourceForeignCollections(NamespaceString foreignNss)
- : _foreignNssSet{std::move(foreignNss)} {}
+ LiteParsedDocumentSourceForeignCollections(NamespaceString foreignNss,
+ PrivilegeVector privileges)
+ : _foreignNssSet{std::move(foreignNss)}, _requiredPrivileges(std::move(privileges)) {}
- explicit LiteParsedDocumentSourceForeignCollections(
- stdx::unordered_set<NamespaceString> foreignNssSet)
- : _foreignNssSet(std::move(foreignNssSet)) {}
+ LiteParsedDocumentSourceForeignCollections(stdx::unordered_set<NamespaceString> foreignNssSet,
+ PrivilegeVector privileges)
+ : _foreignNssSet(std::move(foreignNssSet)), _requiredPrivileges(std::move(privileges)) {}
stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
return {_foreignNssSet};
}
+ PrivilegeVector requiredPrivileges(bool isMongos) const final {
+ return _requiredPrivileges;
+ }
+
private:
stdx::unordered_set<NamespaceString> _foreignNssSet;
+ PrivilegeVector _requiredPrivileges;
};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/lite_parsed_pipeline.h b/src/mongo/db/pipeline/lite_parsed_pipeline.h
index ca2e9dfda77..07e5c947cc3 100644
--- a/src/mongo/db/pipeline/lite_parsed_pipeline.h
+++ b/src/mongo/db/pipeline/lite_parsed_pipeline.h
@@ -72,6 +72,19 @@ public:
}
/**
+ * Returns a list of the priviliges required for this pipeline.
+ */
+ PrivilegeVector requiredPrivileges(bool isMongos) const {
+ PrivilegeVector requiredPrivileges;
+ for (auto&& spec : _stageSpecs) {
+ Privilege::addPrivilegesToPrivilegeVector(&requiredPrivileges,
+ spec->requiredPrivileges(isMongos));
+ }
+
+ return requiredPrivileges;
+ }
+
+ /**
* Returns true if the pipeline begins with a $collStats stage.
*/
bool startsWithCollStats() const {
diff --git a/src/mongo/shell/explainable.js b/src/mongo/shell/explainable.js
index 0dcc5c6d76e..795326ab9d0 100644
--- a/src/mongo/shell/explainable.js
+++ b/src/mongo/shell/explainable.js
@@ -92,13 +92,9 @@ var Explainable = (function() {
// Explainable operations.
//
- /**
- * Adds "explain: true" to "extraOpts", and then passes through to the regular collection's
- * aggregate helper.
- */
this.aggregate = function(pipeline, extraOpts) {
if (!(pipeline instanceof Array)) {
- // support legacy varargs form. (Also handles db.foo.aggregate())
+ // Support legacy varargs form. (Also handles db.foo.aggregate())
pipeline = Array.from(arguments);
extraOpts = {};
}
@@ -113,6 +109,11 @@ var Explainable = (function() {
extraOpts.explain = true;
return this._collection.aggregate(pipeline, extraOpts);
} else {
+ // The aggregate command requires a cursor field.
+ if (!extraOpts.hasOwnProperty("cursor")) {
+ extraOpts = Object.extend(extraOpts, {cursor: {}});
+ }
+
let aggCmd = Object.extend(
{"aggregate": this._collection.getName(), "pipeline": pipeline}, extraOpts);
let explainCmd = {"explain": aggCmd, "verbosity": this._verbosity};