summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/commands.cpp53
-rw-r--r--src/mongo/db/commands.h33
-rw-r--r--src/mongo/db/commands/explain_cmd.cpp37
-rw-r--r--src/mongo/db/commands/oplog_application_checks.cpp13
-rw-r--r--src/mongo/db/commands/write_commands/write_commands.cpp50
-rw-r--r--src/mongo/db/commands/write_commands/write_commands_common.cpp118
-rw-r--r--src/mongo/db/commands/write_commands/write_commands_common.h9
-rw-r--r--src/mongo/db/service_entry_point_common.cpp2
-rw-r--r--src/mongo/s/commands/cluster_explain_cmd.cpp98
-rw-r--r--src/mongo/s/commands/cluster_write_cmd.cpp28
-rw-r--r--src/mongo/s/commands/strategy.cpp11
11 files changed, 187 insertions, 265 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index 103bd17dbeb..47b3cc15df6 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -339,6 +339,17 @@ void CommandReplyBuilder::reset() {
CommandInvocation::~CommandInvocation() = default;
+void CommandInvocation::checkAuthorization(OperationContext* opCtx,
+ const OpMsgRequest& request) const {
+ const Command* c = definition();
+ Status status = _checkAuthorizationImpl(opCtx, request);
+ if (!status.isOK()) {
+ log(LogComponent::kAccessControl) << status;
+ }
+ CommandHelpers::logAuthViolation(opCtx, c, request, status.code());
+ uassertStatusOK(status);
+}
+
//////////////////////////////////////////////////////////////
// Command
@@ -356,10 +367,8 @@ private:
BSONObjBuilder bob = result->getBodyBuilder();
bool ok = _command->run(opCtx, _dbName, _request->body, bob);
CommandHelpers::appendCommandStatus(bob, ok);
- } catch (const ExceptionFor<ErrorCodes::Unauthorized>&) {
- CommandAuditHook hook(_command);
- audit::logCommandAuthzCheck(
- opCtx->getClient(), *_request, &hook, ErrorCodes::Unauthorized);
+ } catch (const ExceptionFor<ErrorCodes::Unauthorized>& e) {
+ CommandHelpers::logAuthViolation(opCtx, _command, *_request, e.code());
throw;
}
}
@@ -391,7 +400,8 @@ private:
}
void doCheckAuthorization(OperationContext* opCtx) const override {
- uassertStatusOK(_command->checkAuthForRequest(opCtx, *_request));
+ uassertStatusOK(_command->checkAuthForOperation(
+ opCtx, _request->getDatabase().toString(), _request->body));
}
const BSONObj& cmdObj() const {
@@ -442,12 +452,6 @@ Status BasicCommand::explain(OperationContext* opCtx,
return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()};
}
-Status BasicCommand::checkAuthForRequest(OperationContext* opCtx,
- const OpMsgRequest& request) const {
- CommandHelpers::uassertNoDocumentSequences(getName(), request);
- return checkAuthForOperation(opCtx, request.getDatabase().toString(), request.body);
-}
-
Status BasicCommand::checkAuthForOperation(OperationContext* opCtx,
const std::string& dbname,
const BSONObj& cmdObj) const {
@@ -464,10 +468,10 @@ Status BasicCommand::checkAuthForCommand(Client* client,
return Status(ErrorCodes::Unauthorized, "unauthorized");
}
-static Status _checkAuthorizationImpl(Command* c,
- OperationContext* opCtx,
- const OpMsgRequest& request) {
+Status CommandInvocation::_checkAuthorizationImpl(OperationContext* opCtx,
+ const OpMsgRequest& request) const {
namespace mmb = mutablebson;
+ const Command* c = definition();
auto client = opCtx->getClient();
auto dbname = request.getDatabase();
if (c->adminOnly() && dbname != "admin") {
@@ -476,7 +480,14 @@ static Status _checkAuthorizationImpl(Command* c,
<< " may only be run against the admin database.");
}
if (AuthorizationSession::get(client)->getAuthorizationManager().isAuthEnabled()) {
- Status status = c->checkAuthForRequest(opCtx, request);
+ Status status = [&] {
+ try {
+ doCheckAuthorization(opCtx);
+ return Status::OK();
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+ }();
if (status == ErrorCodes::Unauthorized) {
mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled);
c->redactForLogging(&cmdToLog);
@@ -496,18 +507,6 @@ static Status _checkAuthorizationImpl(Command* c,
return Status::OK();
}
-Status Command::checkAuthorization(Command* c,
- OperationContext* opCtx,
- const OpMsgRequest& request) {
- Status status = _checkAuthorizationImpl(c, opCtx, request);
- if (!status.isOK()) {
- log(LogComponent::kAccessControl) << status;
- }
- CommandAuditHook hook(c);
- audit::logCommandAuthzCheck(opCtx->getClient(), request, &hook, status.code());
- return status;
-}
-
void Command::generateHelpResponse(OperationContext* opCtx,
rpc::ReplyBuilderInterface* replyBuilder,
const Command& command) {
diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h
index 3fa2181b569..b01d366d866 100644
--- a/src/mongo/db/commands.h
+++ b/src/mongo/db/commands.h
@@ -305,13 +305,6 @@ public:
}
/**
- * Checks if the client associated with the given OperationContext is authorized to run this
- * command.
- */
- virtual Status checkAuthForRequest(OperationContext* opCtx,
- const OpMsgRequest& request) const = 0;
-
- /**
* Redacts "cmdObj" in-place to a form suitable for writing to logs.
*
* The default implementation does nothing.
@@ -378,18 +371,6 @@ public:
rpc::ReplyBuilderInterface* replyBuilder,
const Command& command);
- /**
- * Checks to see if the client executing "opCtx" is authorized to run the given command with the
- * given parameters on the given named database.
- *
- * Returns Status::OK() if the command is authorized. Most likely returns
- * ErrorCodes::Unauthorized otherwise, but any return other than Status::OK implies not
- * authorized.
- */
- static Status checkAuthorization(Command* c,
- OperationContext* opCtx,
- const OpMsgRequest& request);
-
private:
// Counters for how many times this command has been executed and failed
Counter64 _commandsExecuted;
@@ -499,8 +480,9 @@ public:
* the client executing "opCtx" is authorized to run the given command
* with the given parameters on the given named database.
* Note: nonvirtual.
+ * The 'request' must outlive this CommandInvocation.
*/
- void checkAuthorization(OperationContext* opCtx) const;
+ void checkAuthorization(OperationContext* opCtx, const OpMsgRequest& request) const;
protected:
ResourcePattern resourcePattern() const;
@@ -512,6 +494,8 @@ private:
*/
virtual void doCheckAuthorization(OperationContext* opCtx) const = 0;
+ Status _checkAuthorizationImpl(OperationContext* opCtx, const OpMsgRequest& request) const;
+
const Command* const _definition;
};
@@ -627,15 +611,6 @@ private:
// The default implementation of addRequiredPrivileges should never be hit.
fassertFailed(16940);
}
-
- //
- // Methods provided for subclasses if they implement above interface.
- //
-
- /**
- * Calls checkAuthForOperation.
- */
- Status checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const final;
};
/**
diff --git a/src/mongo/db/commands/explain_cmd.cpp b/src/mongo/db/commands/explain_cmd.cpp
index 38bcfc2c7f5..0b2ad475968 100644
--- a/src/mongo/db/commands/explain_cmd.cpp
+++ b/src/mongo/db/commands/explain_cmd.cpp
@@ -87,37 +87,13 @@ public:
return explainedCommand->parseNs(dbname, explainedObj);
}
- /**
- * You are authorized to run an explain if you are authorized to run
- * the command that you are explaining. The auth check is performed recursively
- * on the nested command.
- */
- Status checkAuthForRequest(OperationContext* opCtx,
- const OpMsgRequest& request) const override {
- CommandHelpers::uassertNoDocumentSequences(getName(), request);
- const BSONObj& cmdObj = request.body;
- if (Object != cmdObj.firstElement().type()) {
- return Status(ErrorCodes::BadValue, "explain command requires a nested object");
- }
- auto explainedObj = cmdObj.firstElement().Obj();
- auto explainedCommand = CommandHelpers::findCommand(explainedObj.firstElementFieldName());
- if (!explainedCommand) {
- return Status(ErrorCodes::CommandNotFound,
- str::stream() << "unknown command: "
- << explainedObj.firstElementFieldName());
- }
- return explainedCommand->checkAuthForRequest(
- opCtx,
- OpMsgRequest::fromDBAndBody(request.getDatabase().toString(), std::move(explainedObj)));
- }
-
private:
class Invocation;
};
class CmdExplain::Invocation final : public CommandInvocation {
public:
- Invocation(CmdExplain* explainCommand,
+ Invocation(const CmdExplain* explainCommand,
const OpMsgRequest& request,
ExplainOptions::Verbosity verbosity,
std::unique_ptr<OpMsgRequest> innerRequest,
@@ -163,8 +139,13 @@ public:
return command()->secondaryAllowed(context);
}
+ /**
+ * You are authorized to run an explain if you are authorized to run
+ * the command that you are explaining. The auth check is performed recursively
+ * on the nested command.
+ */
void doCheckAuthorization(OperationContext* opCtx) const override {
- uassertStatusOK(command()->checkAuthForRequest(opCtx, *_outerRequest));
+ _innerInvocation->checkAuthorization(opCtx, *_innerRequest);
}
private:
@@ -186,6 +167,9 @@ std::unique_ptr<CommandInvocation> CmdExplain::parse(OperationContext* opCtx,
std::string dbname = request.getDatabase().toString();
const BSONObj& cmdObj = request.body;
ExplainOptions::Verbosity verbosity = uassertStatusOK(ExplainOptions::parseCmdBSON(cmdObj));
+ uassert(ErrorCodes::BadValue,
+ "explain command requires a nested object",
+ cmdObj.firstElement().type() == Object);
auto explainedObj = cmdObj.firstElement().Obj();
if (auto innerDb = explainedObj["$db"]) {
uassert(ErrorCodes::InvalidNamespace,
@@ -199,7 +183,6 @@ std::unique_ptr<CommandInvocation> CmdExplain::parse(OperationContext* opCtx,
str::stream() << "Explain failed due to unknown command: "
<< explainedObj.firstElementFieldName(),
explainedCommand);
-
auto innerRequest =
stdx::make_unique<OpMsgRequest>(OpMsgRequest::fromDBAndBody(dbname, explainedObj));
auto innerInvocation = explainedCommand->parse(opCtx, *innerRequest);
diff --git a/src/mongo/db/commands/oplog_application_checks.cpp b/src/mongo/db/commands/oplog_application_checks.cpp
index 4ef23c19a2c..1a001805f55 100644
--- a/src/mongo/db/commands/oplog_application_checks.cpp
+++ b/src/mongo/db/commands/oplog_application_checks.cpp
@@ -89,8 +89,17 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
dbNameForAuthCheck = "admin";
}
- return Command::checkAuthorization(
- commandInOplogEntry, opCtx, OpMsgRequest::fromDBAndBody(dbNameForAuthCheck, o));
+ // TODO reuse the parse result for when we run() later. Note that when running,
+ // we must use a potentially different dbname.
+ return [&] {
+ try {
+ auto request = OpMsgRequest::fromDBAndBody(dbNameForAuthCheck, o);
+ commandInOplogEntry->parse(opCtx, request)->checkAuthorization(opCtx, request);
+ return Status::OK();
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+ }();
}
if (opType == "i"_sd) {
diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp
index 0f9919e038a..6f0f2c72a6e 100644
--- a/src/mongo/db/commands/write_commands/write_commands.cpp
+++ b/src/mongo/db/commands/write_commands/write_commands.cpp
@@ -71,17 +71,6 @@ void redactTooLongLog(mutablebson::Document* cmdObj, StringData fieldName) {
}
}
-Status checkAuthForWriteCommand(Client* client,
- BatchedCommandRequest::BatchType batchType,
- const OpMsgRequest& request) {
- Status status =
- auth::checkAuthForWriteCommand(AuthorizationSession::get(client), batchType, request);
- if (!status.isOK()) {
- LastError::get(client).setLastError(status.code(), status.reason());
- }
- return status;
-}
-
bool shouldSkipOutput(OperationContext* opCtx) {
const WriteConcernOptions& writeConcern = opCtx->getWriteConcern();
return writeConcern.wMode.empty() && writeConcern.wNumNodes == 0 &&
@@ -202,7 +191,7 @@ public:
explicit WriteCommand(StringData name) : Command(name) {}
std::unique_ptr<CommandInvocation> parse(OperationContext* opCtx,
- const OpMsgRequest& request) override;
+ const OpMsgRequest& request) final;
AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
return AllowedOnSecondary::kNever;
@@ -229,6 +218,8 @@ public:
private:
class Invocation;
+
+ virtual BatchedCommandRequest::BatchType writeType() const = 0;
};
class WriteCommand::Invocation : public CommandInvocation {
@@ -277,7 +268,13 @@ private:
}
void doCheckAuthorization(OperationContext* opCtx) const final {
- uassertStatusOK(command()->checkAuthForRequest(opCtx, *_request));
+ try {
+ auth::checkAuthForWriteCommand(
+ AuthorizationSession::get(opCtx->getClient()), command()->writeType(), *_request);
+ } catch (const DBException& e) {
+ LastError::get(opCtx->getClient()).setLastError(e.code(), e.reason());
+ throw;
+ }
}
const WriteCommand* command() const {
@@ -306,11 +303,6 @@ public:
return "insert documents";
}
- Status checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const final {
- return checkAuthForWriteCommand(
- opCtx->getClient(), BatchedCommandRequest::BatchType_Insert, request);
- }
-
void runImpl(OperationContext* opCtx,
const OpMsgRequest& request,
BSONObjBuilder& result) const final {
@@ -323,6 +315,10 @@ public:
std::move(reply),
&result);
}
+
+ BatchedCommandRequest::BatchType writeType() const override {
+ return BatchedCommandRequest::BatchType_Insert;
+ }
} cmdInsert;
class CmdUpdate final : public WriteCommand {
@@ -337,11 +333,6 @@ public:
return "update documents";
}
- Status checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const final {
- return checkAuthForWriteCommand(
- opCtx->getClient(), BatchedCommandRequest::BatchType_Update, request);
- }
-
void runImpl(OperationContext* opCtx,
const OpMsgRequest& request,
BSONObjBuilder& result) const final {
@@ -388,6 +379,10 @@ public:
Explain::explainStages(exec.get(), collection.getCollection(), verbosity, out);
return Status::OK();
}
+
+ BatchedCommandRequest::BatchType writeType() const override {
+ return BatchedCommandRequest::BatchType_Update;
+ }
} cmdUpdate;
class CmdDelete final : public WriteCommand {
@@ -402,11 +397,6 @@ public:
return "delete documents";
}
- Status checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const final {
- return checkAuthForWriteCommand(
- opCtx->getClient(), BatchedCommandRequest::BatchType_Delete, request);
- }
-
void runImpl(OperationContext* opCtx,
const OpMsgRequest& request,
BSONObjBuilder& result) const final {
@@ -449,6 +439,10 @@ public:
Explain::explainStages(exec.get(), collection.getCollection(), verbosity, out);
return Status::OK();
}
+
+ BatchedCommandRequest::BatchType writeType() const override {
+ return BatchedCommandRequest::BatchType_Delete;
+ }
} cmdDelete;
} // namespace
diff --git a/src/mongo/db/commands/write_commands/write_commands_common.cpp b/src/mongo/db/commands/write_commands/write_commands_common.cpp
index 98acc3dd66b..5abef4c1db6 100644
--- a/src/mongo/db/commands/write_commands/write_commands_common.cpp
+++ b/src/mongo/db/commands/write_commands/write_commands_common.cpp
@@ -30,6 +30,7 @@
#include "mongo/db/commands/write_commands/write_commands_common.h"
+#include <algorithm>
#include <string>
#include <vector>
@@ -44,52 +45,28 @@ namespace mongo {
namespace auth {
namespace {
-using write_ops::Delete;
-using write_ops::Insert;
-using write_ops::Update;
-using write_ops::UpdateOpEntry;
-
-/**
- * Helper to determine whether or not there are any upserts in the batch
- */
-bool containsUpserts(const std::vector<UpdateOpEntry>& updates) {
- for (auto&& update : updates) {
- if (update.getUpsert())
- return true;
- }
-
- return false;
-}
-
/**
* Helper to extract the namespace being indexed from a raw BSON write command.
*
* TODO: Remove when we have parsing hooked before authorization.
*/
-StatusWith<NamespaceString> getIndexedNss(const std::vector<BSONObj>& documentsToInsert) {
- if (documentsToInsert.empty()) {
- return {ErrorCodes::FailedToParse, "index write batch is empty"};
- }
-
- const std::string nsToIndex = documentsToInsert.front()["ns"].str();
- if (nsToIndex.empty()) {
- return {ErrorCodes::FailedToParse,
- "index write batch contains an invalid index descriptor"};
- }
-
- if (documentsToInsert.size() != 1) {
- return {ErrorCodes::FailedToParse,
- "index write batches may only contain a single index descriptor"};
- }
-
- return {NamespaceString(std::move(nsToIndex))};
+NamespaceString getIndexedNss(const std::vector<BSONObj>& documentsToInsert) {
+ uassert(ErrorCodes::FailedToParse, "index write batch is empty", !documentsToInsert.empty());
+ std::string nsToIndex = documentsToInsert.front()["ns"].str();
+ uassert(ErrorCodes::FailedToParse,
+ "index write batch contains an invalid index descriptor",
+ !nsToIndex.empty());
+ uassert(ErrorCodes::FailedToParse,
+ "index write batches may only contain a single index descriptor",
+ documentsToInsert.size() == 1);
+ return NamespaceString(std::move(nsToIndex));
}
} // namespace
-Status checkAuthForWriteCommand(AuthorizationSession* authzSession,
- BatchedCommandRequest::BatchType cmdType,
- const OpMsgRequest& request) {
+void checkAuthForWriteCommand(AuthorizationSession* authzSession,
+ BatchedCommandRequest::BatchType cmdType,
+ const OpMsgRequest& request) {
std::vector<Privilege> privileges;
ActionSet actionsOnCommandNSS;
@@ -98,46 +75,49 @@ Status checkAuthForWriteCommand(AuthorizationSession* authzSession,
}
NamespaceString cmdNSS;
- if (cmdType == BatchedCommandRequest::BatchType_Insert) {
- auto op = Insert::parse(IDLParserErrorContext("insert"), request);
- cmdNSS = op.getNamespace();
- if (!op.getNamespace().isSystemDotIndexes()) {
- actionsOnCommandNSS.addAction(ActionType::insert);
- } else {
- // Special-case indexes until we have a command
- const auto swNssToIndex = getIndexedNss(op.getDocuments());
- if (!swNssToIndex.isOK()) {
- return swNssToIndex.getStatus();
- }
- const auto& nssToIndex = swNssToIndex.getValue();
- privileges.push_back(
- Privilege(ResourcePattern::forExactNamespace(nssToIndex), ActionType::createIndex));
+ switch (cmdType) {
+ case BatchedCommandRequest::BatchType_Insert: {
+ auto op = write_ops::Insert::parse(IDLParserErrorContext("insert"), request);
+ cmdNSS = op.getNamespace();
+ if (!op.getNamespace().isSystemDotIndexes()) {
+ actionsOnCommandNSS.addAction(ActionType::insert);
+ } else {
+ // Special-case indexes until we have a command
+ auto nssToIndex = getIndexedNss(op.getDocuments());
+ privileges.push_back(Privilege(ResourcePattern::forExactNamespace(nssToIndex),
+ ActionType::createIndex));
+ }
+ break;
}
- } else if (cmdType == BatchedCommandRequest::BatchType_Update) {
- auto op = Update::parse(IDLParserErrorContext("update"), request);
- cmdNSS = op.getNamespace();
- actionsOnCommandNSS.addAction(ActionType::update);
-
- // Upsert also requires insert privs
- if (containsUpserts(op.getUpdates())) {
- actionsOnCommandNSS.addAction(ActionType::insert);
+ case BatchedCommandRequest::BatchType_Update: {
+ auto op = write_ops::Update::parse(IDLParserErrorContext("update"), request);
+ cmdNSS = op.getNamespace();
+ actionsOnCommandNSS.addAction(ActionType::update);
+ // Upsert also requires insert privs
+ const auto& updates = op.getUpdates();
+ if (std::any_of(
+ updates.begin(), updates.end(), [](auto&& x) { return x.getUpsert(); })) {
+ actionsOnCommandNSS.addAction(ActionType::insert);
+ }
+ break;
+ }
+ case BatchedCommandRequest::BatchType_Delete: {
+ auto op = write_ops::Delete::parse(IDLParserErrorContext("delete"), request);
+ cmdNSS = op.getNamespace();
+ actionsOnCommandNSS.addAction(ActionType::remove);
+ break;
}
- } else {
- fassert(17251, cmdType == BatchedCommandRequest::BatchType_Delete);
- auto op = Delete::parse(IDLParserErrorContext("delete"), request);
- cmdNSS = op.getNamespace();
- actionsOnCommandNSS.addAction(ActionType::remove);
}
if (!actionsOnCommandNSS.empty()) {
- privileges.emplace_back(ResourcePattern::forExactNamespace(cmdNSS), actionsOnCommandNSS);
+ privileges.push_back(
+ Privilege(ResourcePattern::forExactNamespace(cmdNSS), actionsOnCommandNSS));
}
- if (authzSession->isAuthorizedForPrivileges(privileges))
- return Status::OK();
-
- return Status(ErrorCodes::Unauthorized, "unauthorized");
+ uassert(ErrorCodes::Unauthorized,
+ "unauthorized",
+ authzSession->isAuthorizedForPrivileges(privileges));
}
} // namespace auth
diff --git a/src/mongo/db/commands/write_commands/write_commands_common.h b/src/mongo/db/commands/write_commands/write_commands_common.h
index 897ee283fe1..03685223b57 100644
--- a/src/mongo/db/commands/write_commands/write_commands_common.h
+++ b/src/mongo/db/commands/write_commands/write_commands_common.h
@@ -41,9 +41,12 @@
namespace mongo {
namespace auth {
-Status checkAuthForWriteCommand(AuthorizationSession* authzSession,
- BatchedCommandRequest::BatchType cmdType,
- const OpMsgRequest& request);
+/**
+ * Throws if write command shouldn't proceed.
+ */
+void checkAuthForWriteCommand(AuthorizationSession* authzSession,
+ BatchedCommandRequest::BatchType cmdType,
+ const OpMsgRequest& request);
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index 8f9fe6b41b8..021933e87e7 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -648,7 +648,7 @@ void execCommandDatabase(OperationContext* opCtx,
}
ImpersonationSessionGuard guard(opCtx);
- uassertStatusOK(Command::checkAuthorization(command, opCtx, request));
+ invocation->checkAuthorization(opCtx, request);
const bool iAmPrimary = replCoord->canAcceptWritesForDatabase_UNSAFE(opCtx, dbname);
diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp
index 24254f24054..b871c51423f 100644
--- a/src/mongo/s/commands/cluster_explain_cmd.cpp
+++ b/src/mongo/s/commands/cluster_explain_cmd.cpp
@@ -76,41 +76,13 @@ public:
return "explain database reads and writes";
}
- /**
- * You are authorized to run an explain if you are authorized to run
- * the command that you are explaining. The auth check is performed recursively
- * on the nested command.
- */
- Status checkAuthForRequest(OperationContext* opCtx,
- const OpMsgRequest& request) const override {
- CommandHelpers::uassertNoDocumentSequences(getName(), request);
- const std::string dbname = request.getDatabase().toString();
- const BSONObj& cmdObj = request.body;
-
- if (Object != cmdObj.firstElement().type()) {
- return Status(ErrorCodes::BadValue, "explain command requires a nested object");
- }
-
- BSONObj explainedObj = cmdObj.firstElement().Obj();
-
- auto explainedCommand = CommandHelpers::findCommand(explainedObj.firstElementFieldName());
- if (!explainedCommand) {
- return Status(ErrorCodes::CommandNotFound,
- str::stream() << "unknown command: "
- << explainedObj.firstElementFieldName());
- }
-
- return explainedCommand->checkAuthForRequest(
- opCtx, OpMsgRequest::fromDBAndBody(dbname, std::move(explainedObj)));
- }
-
private:
class Invocation;
};
class ClusterExplainCmd::Invocation final : public CommandInvocation {
public:
- Invocation(ClusterExplainCmd* explainCommand,
+ Invocation(const ClusterExplainCmd* explainCommand,
const OpMsgRequest& request,
ExplainOptions::Verbosity verbosity,
std::unique_ptr<OpMsgRequest> innerRequest,
@@ -153,8 +125,13 @@ private:
return command()->secondaryAllowed(context);
}
+ /**
+ * You are authorized to run an explain if you are authorized to run
+ * the command that you are explaining. The auth check is performed recursively
+ * on the nested command.
+ */
void doCheckAuthorization(OperationContext* opCtx) const override {
- uassertStatusOK(command()->checkAuthForRequest(opCtx, *_outerRequest));
+ _innerInvocation->checkAuthorization(opCtx, *_innerRequest);
}
const ClusterExplainCmd* command() const {
@@ -169,6 +146,37 @@ private:
std::unique_ptr<CommandInvocation> _innerInvocation;
};
+/**
+ * Synthesize a BSONObj for the command to be explained.
+ * To do this we must copy generic arguments from the enclosing explain command.
+ */
+BSONObj makeExplainedObj(const BSONObj& outerObj, StringData dbName) {
+ const auto& first = outerObj.firstElement();
+ uassert(
+ ErrorCodes::BadValue, "explain command requires a nested object", first.type() == Object);
+ const BSONObj& innerObj = first.Obj();
+
+ if (auto innerDb = innerObj["$db"]) {
+ uassert(ErrorCodes::InvalidNamespace,
+ str::stream() << "Mismatched $db in explain command. Expected " << dbName
+ << " but got "
+ << innerDb.checkAndGetStringData(),
+ innerDb.checkAndGetStringData() == dbName);
+ }
+
+ BSONObjBuilder bob;
+ bob.appendElements(innerObj);
+ for (auto outerElem : outerObj) {
+ // If the argument is in both the inner and outer command, we currently let the
+ // inner version take precedence.
+ const auto name = outerElem.fieldNameStringData();
+ if (isGenericArgument(name) && !innerObj.hasField(name)) {
+ bob.append(outerElem);
+ }
+ }
+ return bob.obj();
+}
+
std::unique_ptr<CommandInvocation> ClusterExplainCmd::parse(OperationContext* opCtx,
const OpMsgRequest& request) {
CommandHelpers::uassertNoDocumentSequences(getName(), request);
@@ -178,37 +186,13 @@ std::unique_ptr<CommandInvocation> ClusterExplainCmd::parse(OperationContext* op
// This is the nested command which we are explaining. We need to propagate generic
// arguments into the inner command since it is what is passed to the virtual
- // Command::explain() method.
- const BSONObj explainedObj = [&] {
- const auto innerObj = cmdObj.firstElement().Obj();
- if (auto innerDb = innerObj["$db"]) {
- uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "Mismatched $db in explain command. Expected " << dbName
- << " but got "
- << innerDb.checkAndGetStringData(),
- innerDb.checkAndGetStringData() == dbName);
- }
-
- BSONObjBuilder bob;
- bob.appendElements(innerObj);
- for (auto outerElem : cmdObj) {
- // If the argument is in both the inner and outer command, we currently let the
- // inner version take precedence.
- const auto name = outerElem.fieldNameStringData();
- if (isGenericArgument(name) && !innerObj.hasField(name)) {
- bob.append(outerElem);
- }
- }
- return bob.obj();
- }();
-
+ // CommandInvocation::explain() method.
+ const BSONObj explainedObj = makeExplainedObj(cmdObj, dbName);
const std::string cmdName = explainedObj.firstElementFieldName();
auto explainedCommand = CommandHelpers::findCommand(cmdName);
uassert(ErrorCodes::CommandNotFound,
str::stream() << "Explain failed due to unknown command: " << cmdName,
- explainedCommand != nullptr);
-
- // Actually call the nested command's explain(...) method.
+ explainedCommand);
auto innerRequest = std::make_unique<OpMsgRequest>(OpMsg{explainedObj});
auto innerInvocation = explainedCommand->parse(opCtx, *innerRequest);
return stdx::make_unique<Invocation>(
diff --git a/src/mongo/s/commands/cluster_write_cmd.cpp b/src/mongo/s/commands/cluster_write_cmd.cpp
index 457b8ba02ec..a3291f2fecb 100644
--- a/src/mongo/s/commands/cluster_write_cmd.cpp
+++ b/src/mongo/s/commands/cluster_write_cmd.cpp
@@ -159,18 +159,6 @@ public:
MONGO_UNREACHABLE;
}
- Status checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const final {
- Status status = auth::checkAuthForWriteCommand(
- AuthorizationSession::get(opCtx->getClient()), _writeType, request);
-
- // TODO: Remove this when we standardize GLE reporting from commands
- if (!status.isOK()) {
- LastError::get(opCtx->getClient()).setLastError(status.code(), status.reason());
- }
-
- return status;
- }
-
Status explainImpl(OperationContext* opCtx,
const OpMsgRequest& request,
ExplainOptions::Verbosity verbosity,
@@ -349,7 +337,7 @@ private:
return dispatchStatus;
}
- // Type of batch (e.g. insert, update).
+ // Type of batch: insert, update, or delete.
const BatchedCommandRequest::BatchType _writeType;
};
@@ -396,7 +384,13 @@ private:
}
void doCheckAuthorization(OperationContext* opCtx) const override {
- uassertStatusOK(command()->checkAuthForRequest(opCtx, *_request));
+ try {
+ auth::checkAuthForWriteCommand(
+ AuthorizationSession::get(opCtx->getClient()), command()->_writeType, *_request);
+ } catch (const DBException& e) {
+ LastError::get(opCtx->getClient()).setLastError(e.code(), e.reason());
+ throw;
+ }
}
const ClusterWriteCmd* command() const {
@@ -417,7 +411,7 @@ std::unique_ptr<CommandInvocation> ClusterWriteCmd::parse(OperationContext* opCt
parseRequest(_writeType, request));
}
-class ClusterCmdInsert : public ClusterWriteCmd {
+class ClusterCmdInsert final : public ClusterWriteCmd {
public:
ClusterCmdInsert() : ClusterWriteCmd("insert", BatchedCommandRequest::BatchType_Insert) {}
@@ -427,7 +421,7 @@ public:
} clusterInsertCmd;
-class ClusterCmdUpdate : public ClusterWriteCmd {
+class ClusterCmdUpdate final : public ClusterWriteCmd {
public:
ClusterCmdUpdate() : ClusterWriteCmd("update", BatchedCommandRequest::BatchType_Update) {}
@@ -437,7 +431,7 @@ public:
} clusterUpdateCmd;
-class ClusterCmdDelete : public ClusterWriteCmd {
+class ClusterCmdDelete final : public ClusterWriteCmd {
public:
ClusterCmdDelete() : ClusterWriteCmd("delete", BatchedCommandRequest::BatchType_Delete) {}
diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp
index beef205dc13..6e76510032a 100644
--- a/src/mongo/s/commands/strategy.cpp
+++ b/src/mongo/s/commands/strategy.cpp
@@ -179,10 +179,13 @@ void execCommandClient(OperationContext* opCtx,
topLevelFields[fieldName]++ == 0);
}
- Status status = Command::checkAuthorization(c, opCtx, request);
- if (!status.isOK()) {
+ auto invocation = c->parse(opCtx, request);
+
+ try {
+ invocation->checkAuthorization(opCtx, request);
+ } catch (const DBException& e) {
auto body = result->getBodyBuilder();
- CommandHelpers::appendCommandStatus(body, status);
+ CommandHelpers::appendCommandStatus(body, e.toStatus());
return;
}
@@ -200,8 +203,6 @@ void execCommandClient(OperationContext* opCtx,
return;
}
- auto invocation = c->parse(opCtx, request);
-
bool supportsWriteConcern = invocation->supportsWriteConcern();
if (!supportsWriteConcern && !wcResult.getValue().usedDefault) {
// This command doesn't do writes so it should not be passed a writeConcern.