diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/commands.cpp | 53 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 33 | ||||
-rw-r--r-- | src/mongo/db/commands/explain_cmd.cpp | 37 | ||||
-rw-r--r-- | src/mongo/db/commands/oplog_application_checks.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands.cpp | 50 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands_common.cpp | 118 | ||||
-rw-r--r-- | src/mongo/db/commands/write_commands/write_commands_common.h | 9 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 2 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_explain_cmd.cpp | 98 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_write_cmd.cpp | 28 | ||||
-rw-r--r-- | src/mongo/s/commands/strategy.cpp | 11 |
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. |