diff options
author | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-09-26 12:53:47 -0400 |
---|---|---|
committer | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-09-26 14:10:50 -0400 |
commit | 160a15c25fc59a74cc66cd593d0381741630776f (patch) | |
tree | dc6f623bbc248863a8fdf33f0c5dffc6ac96d0d4 | |
parent | 27b2b2c926ff43f3111adef5a99537260a1b44ef (diff) | |
download | mongo-160a15c25fc59a74cc66cd593d0381741630776f.tar.gz |
SERVER-25994: Make applyOps check for specific permissions
-rw-r--r-- | src/mongo/bson/util/bson_check.h | 14 | ||||
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 46 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.h | 11 | ||||
-rw-r--r-- | src/mongo/db/commands.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 59 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 17 | ||||
-rw-r--r-- | src/mongo/db/commands/apply_ops_cmd.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/commands/apply_ops_cmd_common.cpp | 166 | ||||
-rw-r--r-- | src/mongo/db/commands/apply_ops_cmd_common.h | 46 | ||||
-rw-r--r-- | src/mongo/db/commands/dbcommands.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/commands/explain_cmd.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/ops/write_ops_parsers.cpp | 35 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_explain_cmd.cpp | 8 | ||||
-rw-r--r-- | src/mongo/s/commands/commands_public.cpp | 11 | ||||
-rw-r--r-- | src/mongo/s/s_only.cpp | 2 |
18 files changed, 374 insertions, 103 deletions
diff --git a/src/mongo/bson/util/bson_check.h b/src/mongo/bson/util/bson_check.h index dd4f59816ba..e6f2e3c98bc 100644 --- a/src/mongo/bson/util/bson_check.h +++ b/src/mongo/bson/util/bson_check.h @@ -82,4 +82,18 @@ Status bsonCheckOnlyHasFields(StringData objectName, return bsonCheckOnlyHasFields(objectName, o, &legals[0], legals + N); } +/** + * Throws a uassert if the type of the elem does not match that provided in expectedType + */ +inline void checkBSONType(BSONType expectedType, const BSONElement& elem) { + uassert(elem.type() == BSONType::EOO ? ErrorCodes::NoSuchKey : ErrorCodes::TypeMismatch, + str::stream() << "Wrong type for '" << elem.fieldNameStringData() << "'. Expected a " + << typeName(expectedType) + << ", got a " + << typeName(elem.type()) + << '.', + elem.type() == expectedType); +} + + } // namespace mongo diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 555d21845ea..88753097357 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -42,6 +42,7 @@ env.Library('authcore', ['action_set.cpp', '$BUILD_DIR/mongo/bson/mutable/mutable_bson', '$BUILD_DIR/mongo/bson/util/bson_extract', '$BUILD_DIR/mongo/crypto/scramauth', + '$BUILD_DIR/mongo/db/catalog/document_validation', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/ops/update_driver', '$BUILD_DIR/mongo/db/namespace_string', diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index 0a801445c31..b140ca36dff 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -331,12 +331,14 @@ Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, return Status::OK(); } -Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns, +Status AuthorizationSession::checkAuthForInsert(OperationContext* txn, + const NamespaceString& ns, const BSONObj& document) { if (ns.coll() == "system.indexes"_sd) { BSONElement nsElement = document["ns"]; if (nsElement.type() != String) { - return Status(ErrorCodes::Unauthorized, + return Status(nsElement.type() == BSONType::EOO ? ErrorCodes::NoSuchKey + : ErrorCodes::TypeMismatch, "Cannot authorize inserting into " "system.indexes documents without a string-typed \"ns\" field."); } @@ -346,7 +348,11 @@ Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns, str::stream() << "not authorized to create index on " << indexNS.ns()); } } else { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) { + ActionSet required{ActionType::insert}; + if (documentValidationDisabled(txn)) { + required.addAction(ActionType::bypassDocumentValidation); + } + if (!isAuthorizedForActionsOnNamespace(ns, required)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized for insert on " << ns.ns()); } @@ -355,28 +361,34 @@ Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns, return Status::OK(); } -Status AuthorizationSession::checkAuthForUpdate(const NamespaceString& ns, +Status AuthorizationSession::checkAuthForUpdate(OperationContext* txn, + const NamespaceString& ns, const BSONObj& query, const BSONObj& update, bool upsert) { - if (!upsert) { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::update)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for update on " << ns.ns()); - } - } else { - ActionSet required; - required.addAction(ActionType::update); + ActionSet required{ActionType::update}; + StringData operationType = "update"_sd; + + if (upsert) { required.addAction(ActionType::insert); - if (!isAuthorizedForActionsOnNamespace(ns, required)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for upsert on " << ns.ns()); - } + operationType = "upsert"_sd; + } + + if (documentValidationDisabled(txn)) { + required.addAction(ActionType::bypassDocumentValidation); } + + if (!isAuthorizedForActionsOnNamespace(ns, required)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "not authorized for " << operationType << " on " << ns.ns()); + } + return Status::OK(); } -Status AuthorizationSession::checkAuthForDelete(const NamespaceString& ns, const BSONObj& query) { +Status AuthorizationSession::checkAuthForDelete(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& query) { if (!isAuthorizedForActionsOnNamespace(ns, ActionType::remove)) { return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized to remove from " << ns.ns()); diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 3e75e0ed381..42e7ecfe060 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -155,7 +155,8 @@ public: // Checks if this connection has the privileges necessary to perform the given update on the // given namespace. - Status checkAuthForUpdate(const NamespaceString& ns, + Status checkAuthForUpdate(OperationContext* txn, + const NamespaceString& ns, const BSONObj& query, const BSONObj& update, bool upsert); @@ -163,11 +164,15 @@ public: // Checks if this connection has the privileges necessary to insert the given document // to the given namespace. Correctly interprets inserts to system.indexes and performs // the proper auth checks for index building. - Status checkAuthForInsert(const NamespaceString& ns, const BSONObj& document); + Status checkAuthForInsert(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& document); // Checks if this connection has the privileges necessary to perform a delete on the given // namespace. - Status checkAuthForDelete(const NamespaceString& ns, const BSONObj& query); + Status checkAuthForDelete(OperationContext* txn, + const NamespaceString& ns, + const BSONObj& query); // Checks if this connection has the privileges necessary to perform a killCursor on // the identified cursor, supposing that cursor is associated with the supplied namespace diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index f7b526c1f2c..14b99bc08cd 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -205,6 +205,12 @@ void Command::appendCommandWCStatus(BSONObjBuilder& result, } } +Status Command::checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { + return checkAuthForCommand(txn->getClient(), dbname, cmdObj); +} + Status Command::checkAuthForCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj) { @@ -227,17 +233,18 @@ BSONObj Command::getRedactedCopyForLogging(const BSONObj& cmdObj) { } static Status _checkAuthorizationImpl(Command* c, - Client* client, + OperationContext* txn, const std::string& dbname, const BSONObj& cmdObj) { namespace mmb = mutablebson; + auto client = txn->getClient(); if (c->adminOnly() && dbname != "admin") { return Status(ErrorCodes::Unauthorized, str::stream() << c->getName() << " may only be run against the admin database."); } if (AuthorizationSession::get(client)->getAuthorizationManager().isAuthEnabled()) { - Status status = c->checkAuthForCommand(client, dbname, cmdObj); + Status status = c->checkAuthForOperation(txn, dbname, cmdObj); if (status == ErrorCodes::Unauthorized) { mmb::Document cmdToLog(cmdObj, mmb::Document::kInPlaceDisabled); c->redactForLogging(&cmdToLog); @@ -257,16 +264,16 @@ static Status _checkAuthorizationImpl(Command* c, return Status::OK(); } -Status Command::_checkAuthorization(Command* c, - Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { +Status Command::checkAuthorization(Command* c, + OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { namespace mmb = mutablebson; - Status status = _checkAuthorizationImpl(c, client, dbname, cmdObj); + Status status = _checkAuthorizationImpl(c, txn, dbname, cmdObj); if (!status.isOK()) { - log(LogComponent::kAccessControl) << status << std::endl; + log(LogComponent::kAccessControl) << status; } - audit::logCommandAuthzCheck(client, dbname, cmdObj, c, status.code()); + audit::logCommandAuthzCheck(txn->getClient(), dbname, cmdObj, c, status.code()); return status; } diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index 1e16dc77238..dd4aa4a5255 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -216,12 +216,12 @@ public: BSONObjBuilder* out) const; /** - * Checks if the given client is authorized to run this command on database "dbname" - * with the invocation described by "cmdObj". + * Checks if the client associated with the given OperationContext, "txn", is authorized to run + * this command on database "dbname" with the invocation described by "cmdObj". */ - virtual Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); + virtual Status checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj); /** * Redacts "cmdObj" in-place to a form suitable for writing to logs. @@ -281,18 +281,6 @@ public: } protected: - /** - * Appends to "*out" the privileges required to run this command on database "dbname" with - * the invocation described by "cmdObj". New commands shouldn't implement this, they should - * implement checkAuthForCommand instead. - */ - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) { - // The default implementation of addRequiredPrivileges should never be hit. - fassertFailed(16940); - } - static CommandMap* _commands; static CommandMap* _commandsByBestName; @@ -443,20 +431,43 @@ public: */ static bool isUserManagementCommand(const std::string& name); -private: /** - * Checks to see if the client is authorized to run the given command with the given - * parameters on the given named database. + * Checks to see if the client executing "txn" 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, - Client* client, - const std::string& dbname, - const BSONObj& cmdObj); + static Status checkAuthorization(Command* c, + OperationContext* client, + const std::string& dbname, + const BSONObj& cmdObj); +private: + /** + * Checks if the given client is authorized to run this command on database "dbname" + * with the invocation described by "cmdObj". + * + * NOTE: Implement checkAuthForOperation that takes an OperationContext* instead. + */ + virtual Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj); + + /** + * Appends to "*out" the privileges required to run this command on database "dbname" with + * the invocation described by "cmdObj". New commands shouldn't implement this, they should + * implement checkAuthForOperation (which takes an OperationContext*), instead. + */ + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // The default implementation of addRequiredPrivileges should never be hit. + fassertFailed(16940); + } + +private: // The full name of the command const std::string _name; diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 833058ec55f..89fee1b8572 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -136,6 +136,7 @@ env.Library( "write_commands/write_commands.cpp", ], LIBDEPS=[ + 'apply_ops_cmd_common', 'core', 'killcursors_common', '$BUILD_DIR/mongo/base', @@ -175,6 +176,22 @@ env.Library( ) env.Library( + target='apply_ops_cmd_common', + source=[ + 'apply_ops_cmd_common.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/catalog/document_validation', + '$BUILD_DIR/mongo/db/commands', + '$BUILD_DIR/mongo/db/auth/authcore', + '$BUILD_DIR/mongo/db/namespace_string', + '$BUILD_DIR/mongo/db/service_context', + ], +) + + +env.Library( target="list_collections_filter", source=[ 'list_collections_filter.cpp', diff --git a/src/mongo/db/commands/apply_ops_cmd.cpp b/src/mongo/db/commands/apply_ops_cmd.cpp index 21947850f6c..827fdc6c096 100644 --- a/src/mongo/db/commands/apply_ops_cmd.cpp +++ b/src/mongo/db/commands/apply_ops_cmd.cpp @@ -36,12 +36,11 @@ #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_manager_global.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/privilege.h" -#include "mongo/db/auth/resource_pattern.h" #include "mongo/db/catalog/apply_ops.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" +#include "mongo/db/commands/apply_ops_cmd_common.h" #include "mongo/db/commands/dbhash.h" #include "mongo/db/concurrency/write_conflict_exception.h" #include "mongo/db/db_raii.h" @@ -80,11 +79,11 @@ public: "res : ... } ] }"; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) { - // applyOps can do pretty much anything, so require all privileges. - RoleGraph::generateUniversalPrivileges(out); + + virtual Status checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { + return checkAuthForApplyOpsCommand(txn, dbname, cmdObj); } virtual bool run(OperationContext* txn, diff --git a/src/mongo/db/commands/apply_ops_cmd_common.cpp b/src/mongo/db/commands/apply_ops_cmd_common.cpp new file mode 100644 index 00000000000..9367fd55fef --- /dev/null +++ b/src/mongo/db/commands/apply_ops_cmd_common.cpp @@ -0,0 +1,166 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/db/commands/apply_ops_cmd_common.h" + +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/util/bson_check.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/auth/privilege.h" +#include "mongo/db/auth/resource_pattern.h" +#include "mongo/db/catalog/document_validation.h" +#include "mongo/db/client.h" +#include "mongo/db/commands.h" +#include "mongo/db/namespace_string.h" + +namespace mongo { + +namespace { + +Status checkOperationAuthorization(OperationContext* txn, + const std::string& dbname, + const BSONObj& oplogEntry, + bool alwaysUpsert) { + AuthorizationSession* authSession = AuthorizationSession::get(txn->getClient()); + + BSONElement opTypeElem = oplogEntry["op"]; + checkBSONType(BSONType::String, opTypeElem); + const StringData opType = opTypeElem.checkAndGetStringData(); + + if (opType == "n"_sd) { + // oplog notes require cluster permissions, and may not have a ns + if (!authSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::appendOplogNote)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + BSONElement nsElem = oplogEntry["ns"]; + checkBSONType(BSONType::String, nsElem); + NamespaceString ns(oplogEntry["ns"].checkAndGetStringData()); + + BSONElement oElem = oplogEntry["o"]; + checkBSONType(BSONType::Object, oElem); + BSONObj o = oElem.Obj(); + + if (opType == "c"_sd) { + Command* command = Command::findCommand(o.firstElement().fieldNameStringData()); + if (!command) { + return Status(ErrorCodes::FailedToParse, "Unrecognized command in op"); + } + + return Command::checkAuthorization(command, txn, dbname, o); + } + + if (opType == "i"_sd) { + return authSession->checkAuthForInsert(txn, ns, o); + } else if (opType == "u"_sd) { + BSONElement o2Elem = oplogEntry["o2"]; + checkBSONType(BSONType::Object, o2Elem); + BSONObj o2 = o2Elem.Obj(); + + BSONElement bElem = oplogEntry["b"]; + if (!bElem.eoo()) { + checkBSONType(BSONType::Bool, bElem); + } + bool b = bElem.trueValue(); + + const bool upsert = b || alwaysUpsert; + + return authSession->checkAuthForUpdate(txn, ns, o, o2, upsert); + } else if (opType == "d"_sd) { + + return authSession->checkAuthForDelete(txn, ns, o); + } else if (opType == "db"_sd) { + // It seems that 'db' isn't used anymore. Require all actions to prevent casual use. + ActionSet allActions; + allActions.addAllActions(); + if (!authSession->isAuthorizedForActionsOnResource(ResourcePattern::forAnyResource(), + allActions)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + return Status::OK(); + } + + return Status(ErrorCodes::FailedToParse, "Unrecognized opType"); +} + +} // namespace + + +Status checkAuthForApplyOpsCommand(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authSession = AuthorizationSession::get(txn->getClient()); + + + std::vector<Privilege> universalPrivileges; + RoleGraph::generateUniversalPrivileges(&universalPrivileges); + if (!authSession->isAuthorizedForPrivileges(universalPrivileges)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + + + boost::optional<DisableDocumentValidation> maybeDisableValidation; + if (shouldBypassDocumentValidationForCommand(cmdObj)) + maybeDisableValidation.emplace(txn); + + + const bool alwaysUpsert = + cmdObj.hasField("alwaysUpsert") ? cmdObj["alwaysUpsert"].trueValue() : true; + + checkBSONType(BSONType::Array, cmdObj.firstElement()); + for (const BSONElement& e : cmdObj.firstElement().Array()) { + checkBSONType(BSONType::Object, e); + Status status = checkOperationAuthorization(txn, dbname, e.Obj(), alwaysUpsert); + if (!status.isOK()) { + return status; + } + } + + BSONElement preconditions = cmdObj["preCondition"]; + if (!preconditions.eoo()) { + for (const BSONElement& precondition : preconditions.Array()) { + checkBSONType(BSONType::Object, precondition); + BSONElement nsElem = precondition.Obj()["ns"]; + checkBSONType(BSONType::String, nsElem); + NamespaceString nss(nsElem.checkAndGetStringData()); + + if (!authSession->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(nss), ActionType::find)) { + return Status(ErrorCodes::Unauthorized, "Unauthorized to check precondition"); + } + } + } + + return Status::OK(); +} +} // namespace mongo diff --git a/src/mongo/db/commands/apply_ops_cmd_common.h b/src/mongo/db/commands/apply_ops_cmd_common.h new file mode 100644 index 00000000000..2b4856f3f74 --- /dev/null +++ b/src/mongo/db/commands/apply_ops_cmd_common.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include <string> + +namespace mongo { + +class BSONObj; +class OperationContext; +class Status; + +/** + * Returns Status::OK if the associated client is authorized to perform the command in cmdObj. + */ +Status checkAuthForApplyOpsCommand(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj); + +} // namespace mongo diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 3b5f16ce52d..432c630166d 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -1231,8 +1231,7 @@ void Command::execCommand(OperationContext* txn, } ImpersonationSessionGuard guard(txn); - uassertStatusOK( - _checkAuthorization(command, txn->getClient(), dbname, request.getCommandArgs())); + uassertStatusOK(checkAuthorization(command, txn, dbname, request.getCommandArgs())); repl::ReplicationCoordinator* replCoord = repl::ReplicationCoordinator::get(txn->getClient()->getServiceContext()); diff --git a/src/mongo/db/commands/explain_cmd.cpp b/src/mongo/db/commands/explain_cmd.cpp index 09ff0b1d44c..1a53140e965 100644 --- a/src/mongo/db/commands/explain_cmd.cpp +++ b/src/mongo/db/commands/explain_cmd.cpp @@ -89,9 +89,9 @@ public: * the command that you are explaining. The auth check is performed recursively * on the nested command. */ - virtual Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { + virtual Status checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { if (Object != cmdObj.firstElement().type()) { return Status(ErrorCodes::BadValue, "explain command requires a nested object"); } @@ -105,7 +105,7 @@ public: return Status(ErrorCodes::CommandNotFound, ss); } - return commToExplain->checkAuthForCommand(client, dbname, explainObj); + return commToExplain->checkAuthForOperation(txn, dbname, explainObj); } virtual bool run(OperationContext* txn, diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 790c492e7a5..130d32dc594 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -410,7 +410,7 @@ void receivedInsert(OperationContext* txn, const NamespaceString& nsString, Mess invariant(insertOp.ns == nsString); for (const auto& obj : insertOp.documents) { Status status = - AuthorizationSession::get(txn->getClient())->checkAuthForInsert(nsString, obj); + AuthorizationSession::get(txn->getClient())->checkAuthForInsert(txn, nsString, obj); audit::logInsertAuthzCheck(txn->getClient(), nsString, obj, status.code()); uassertStatusOK(status); } @@ -422,9 +422,10 @@ void receivedUpdate(OperationContext* txn, const NamespaceString& nsString, Mess auto& singleUpdate = updateOp.updates[0]; invariant(updateOp.ns == nsString); - Status status = AuthorizationSession::get(txn->getClient()) - ->checkAuthForUpdate( - nsString, singleUpdate.query, singleUpdate.update, singleUpdate.upsert); + Status status = + AuthorizationSession::get(txn->getClient()) + ->checkAuthForUpdate( + txn, nsString, singleUpdate.query, singleUpdate.update, singleUpdate.upsert); audit::logUpdateAuthzCheck(txn->getClient(), nsString, singleUpdate.query, @@ -443,7 +444,7 @@ void receivedDelete(OperationContext* txn, const NamespaceString& nsString, Mess invariant(deleteOp.ns == nsString); Status status = AuthorizationSession::get(txn->getClient()) - ->checkAuthForDelete(nsString, singleDelete.query); + ->checkAuthForDelete(txn, nsString, singleDelete.query); audit::logDeleteAuthzCheck(txn->getClient(), nsString, singleDelete.query, status.code()); uassertStatusOK(status); diff --git a/src/mongo/db/ops/write_ops_parsers.cpp b/src/mongo/db/ops/write_ops_parsers.cpp index a4ade410010..3e7281fcdca 100644 --- a/src/mongo/db/ops/write_ops_parsers.cpp +++ b/src/mongo/db/ops/write_ops_parsers.cpp @@ -30,6 +30,7 @@ #include "mongo/db/ops/write_ops_parsers.h" +#include "mongo/bson/util/bson_check.h" #include "mongo/client/dbclientinterface.h" #include "mongo/db/catalog/document_validation.h" #include "mongo/db/dbmessage.h" @@ -59,16 +60,6 @@ void checkTypeInArray(BSONType expectedType, elem.type() == expectedType); } -void checkType(BSONType expectedType, const BSONElement& elem) { - uassert(ErrorCodes::TypeMismatch, - str::stream() << "Wrong type for '" << elem.fieldNameStringData() << "'. Expected a " - << typeName(expectedType) - << ", got a " - << typeName(elem.type()) - << '.', - elem.type() == expectedType); -} - void checkOpCountForCommand(size_t numOps) { uassert(ErrorCodes::InvalidLength, str::stream() << "Write batch sizes must be between 1 and " << kMaxWriteBatchSize @@ -97,7 +88,7 @@ void parseWriteCommand(StringData dbName, for (BSONElement field : cmd) { if (firstElement) { // The key is the command name and the value is the collection name - checkType(String, field); + checkBSONType(String, field); op->ns = NamespaceString(dbName, field.valueStringData()); firstElement = false; continue; @@ -107,7 +98,7 @@ void parseWriteCommand(StringData dbName, if (fieldName == "bypassDocumentValidation") { op->bypassDocumentValidation = field.trueValue(); } else if (fieldName == "ordered") { - checkType(Bool, field); + checkBSONType(Bool, field); op->continueOnError = !field.Bool(); } else if (fieldName == uniqueFieldName) { haveUniqueField = true; @@ -136,7 +127,7 @@ InsertOp parseInsertCommand(StringData dbName, const BSONObj& cmd) { BSONElement documents; InsertOp op; parseWriteCommand(dbName, cmd, "documents", &documents, &op); - checkType(Array, documents); + checkBSONType(Array, documents); for (auto doc : documents.Obj()) { checkTypeInArray(Object, doc, documents); op.documents.push_back(doc.Obj()); @@ -157,7 +148,7 @@ UpdateOp parseUpdateCommand(StringData dbName, const BSONObj& cmd) { BSONElement updates; UpdateOp op; parseWriteCommand(dbName, cmd, "updates", &updates, &op); - checkType(Array, updates); + checkBSONType(Array, updates); for (auto doc : updates.Obj()) { checkTypeInArray(Object, doc, updates); op.updates.emplace_back(); @@ -168,20 +159,20 @@ UpdateOp parseUpdateCommand(StringData dbName, const BSONObj& cmd) { const StringData fieldName = field.fieldNameStringData(); if (fieldName == "q") { haveQ = true; - checkType(Object, field); + checkBSONType(Object, field); update.query = field.Obj(); } else if (fieldName == "u") { haveU = true; - checkType(Object, field); + checkBSONType(Object, field); update.update = field.Obj(); } else if (fieldName == "collation") { - checkType(Object, field); + checkBSONType(Object, field); update.collation = field.Obj(); } else if (fieldName == "multi") { - checkType(Bool, field); + checkBSONType(Bool, field); update.multi = field.Bool(); } else if (fieldName == "upsert") { - checkType(Bool, field); + checkBSONType(Bool, field); update.upsert = field.Bool(); } else { uasserted(ErrorCodes::FailedToParse, @@ -200,7 +191,7 @@ DeleteOp parseDeleteCommand(StringData dbName, const BSONObj& cmd) { BSONElement deletes; DeleteOp op; parseWriteCommand(dbName, cmd, "deletes", &deletes, &op); - checkType(Array, deletes); + checkBSONType(Array, deletes); for (auto doc : deletes.Obj()) { checkTypeInArray(Object, doc, deletes); op.deletes.emplace_back(); @@ -211,10 +202,10 @@ DeleteOp parseDeleteCommand(StringData dbName, const BSONObj& cmd) { const StringData fieldName = field.fieldNameStringData(); if (fieldName == "q") { haveQ = true; - checkType(Object, field); + checkBSONType(Object, field); del.query = field.Obj(); } else if (fieldName == "collation") { - checkType(Object, field); + checkBSONType(Object, field); del.collation = field.Obj(); } else if (fieldName == "limit") { haveLimit = true; diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index b4456856950..0234ce265c0 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -76,6 +76,7 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/client/parallel', + '$BUILD_DIR/mongo/db/commands/apply_ops_cmd_common', '$BUILD_DIR/mongo/db/commands/killcursors_common', '$BUILD_DIR/mongo/db/pipeline/aggregation', '$BUILD_DIR/mongo/db/views/views', diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp index 0218e257b36..3030589a080 100644 --- a/src/mongo/s/commands/cluster_explain_cmd.cpp +++ b/src/mongo/s/commands/cluster_explain_cmd.cpp @@ -87,9 +87,9 @@ public: * the command that you are explaining. The auth check is performed recursively * on the nested command. */ - virtual Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { + virtual Status checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { if (Object != cmdObj.firstElement().type()) { return Status(ErrorCodes::BadValue, "explain command requires a nested object"); } @@ -103,7 +103,7 @@ public: return Status(ErrorCodes::CommandNotFound, ss); } - return commToExplain->checkAuthForCommand(client, dbname, explainObj); + return commToExplain->checkAuthForOperation(txn, dbname, explainObj); } virtual bool run(OperationContext* txn, diff --git a/src/mongo/s/commands/commands_public.cpp b/src/mongo/s/commands/commands_public.cpp index 6e1ab0682c9..095d29b6717 100644 --- a/src/mongo/s/commands/commands_public.cpp +++ b/src/mongo/s/commands/commands_public.cpp @@ -41,6 +41,7 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/commands.h" +#include "mongo/db/commands/apply_ops_cmd_common.h" #include "mongo/db/commands/copydb.h" #include "mongo/db/commands/rename_collection.h" #include "mongo/db/lasterror.h" @@ -1625,11 +1626,11 @@ public: class ApplyOpsCmd : public PublicGridCommand { public: ApplyOpsCmd() : PublicGridCommand("applyOps") {} - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) { - // applyOps can do pretty much anything, so require all privileges. - RoleGraph::generateUniversalPrivileges(out); + + virtual Status checkAuthForOperation(OperationContext* txn, + const std::string& dbname, + const BSONObj& cmdObj) { + return checkAuthForApplyOpsCommand(txn, dbname, cmdObj); } virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return true; diff --git a/src/mongo/s/s_only.cpp b/src/mongo/s/s_only.cpp index 18db1dd374e..ca5d63625f2 100644 --- a/src/mongo/s/s_only.cpp +++ b/src/mongo/s/s_only.cpp @@ -104,7 +104,7 @@ void Command::execCommandClient(OperationContext* txn, return; } - Status status = _checkAuthorization(c, txn->getClient(), dbname, cmdObj); + Status status = checkAuthorization(c, txn, dbname, cmdObj); if (!status.isOK()) { appendCommandStatus(result, status); return; |