summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2016-09-26 12:53:47 -0400
committerSpencer Jackson <spencer.jackson@mongodb.com>2016-09-26 14:10:50 -0400
commit160a15c25fc59a74cc66cd593d0381741630776f (patch)
treedc6f623bbc248863a8fdf33f0c5dffc6ac96d0d4
parent27b2b2c926ff43f3111adef5a99537260a1b44ef (diff)
downloadmongo-160a15c25fc59a74cc66cd593d0381741630776f.tar.gz
SERVER-25994: Make applyOps check for specific permissions
-rw-r--r--src/mongo/bson/util/bson_check.h14
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/authorization_session.cpp46
-rw-r--r--src/mongo/db/auth/authorization_session.h11
-rw-r--r--src/mongo/db/commands.cpp25
-rw-r--r--src/mongo/db/commands.h59
-rw-r--r--src/mongo/db/commands/SConscript17
-rw-r--r--src/mongo/db/commands/apply_ops_cmd.cpp13
-rw-r--r--src/mongo/db/commands/apply_ops_cmd_common.cpp166
-rw-r--r--src/mongo/db/commands/apply_ops_cmd_common.h46
-rw-r--r--src/mongo/db/commands/dbcommands.cpp3
-rw-r--r--src/mongo/db/commands/explain_cmd.cpp8
-rw-r--r--src/mongo/db/instance.cpp11
-rw-r--r--src/mongo/db/ops/write_ops_parsers.cpp35
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_explain_cmd.cpp8
-rw-r--r--src/mongo/s/commands/commands_public.cpp11
-rw-r--r--src/mongo/s/s_only.cpp2
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;