diff options
author | Billy Donahue <billy.donahue@mongodb.com> | 2018-04-05 16:53:12 -0400 |
---|---|---|
committer | Billy Donahue <billy.donahue@mongodb.com> | 2018-04-05 17:00:34 -0400 |
commit | 8b5a91133ea5ab272fed72b5d2ca0574899c270b (patch) | |
tree | 5dfd80a40781d1a3d0a9d8fc106795153f69f0b4 /src/mongo/db/commands | |
parent | bafdf4c440267d22bbf8052689fbcc4f1d0a123d (diff) | |
download | mongo-8b5a91133ea5ab272fed72b5d2ca0574899c270b.tar.gz |
SERVER-33881 move checkAuthorization to CommandInvocation
Explain commands already have a parsed _innerInvocation so they
can now use that for their auth check.
Diffstat (limited to 'src/mongo/db/commands')
5 files changed, 98 insertions, 129 deletions
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 |