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-10-03 11:37:23 -0400
commit13bb20d6ee2fd6b0d7e4128ce0ed0c8d8fe5173f (patch)
tree82d17fc79448f96a311a94c9b9d9287b43cf7b7d
parent0881cbfcd67424e9c1525ff7ede7ff099263c57a (diff)
downloadmongo-13bb20d6ee2fd6b0d7e4128ce0ed0c8d8fe5173f.tar.gz
SERVER-25994: Make applyOps check for specific permissions
-rw-r--r--src/mongo/bson/util/bson_check.h11
-rw-r--r--src/mongo/db/SConscript2
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/authorization_session.cpp51
-rw-r--r--src/mongo/db/auth/authorization_session.h11
-rw-r--r--src/mongo/db/catalog/SConscript10
-rw-r--r--src/mongo/db/commands.cpp25
-rw-r--r--src/mongo/db/commands.h57
-rw-r--r--src/mongo/db/commands/SConscript16
-rw-r--r--src/mongo/db/commands/apply_ops.cpp14
-rw-r--r--src/mongo/db/commands/apply_ops_cmd_common.cpp165
-rw-r--r--src/mongo/db/commands/apply_ops_cmd_common.h46
-rw-r--r--src/mongo/db/commands/explain_cmd.cpp8
-rw-r--r--src/mongo/db/dbcommands.cpp3
-rw-r--r--src/mongo/db/instance.cpp8
-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
19 files changed, 369 insertions, 81 deletions
diff --git a/src/mongo/bson/util/bson_check.h b/src/mongo/bson/util/bson_check.h
index 4f2585e9e75..10a04e005fe 100644
--- a/src/mongo/bson/util/bson_check.h
+++ b/src/mongo/bson/util/bson_check.h
@@ -81,4 +81,15 @@ 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/SConscript b/src/mongo/db/SConscript
index 8932a2f6555..f3012e2a04f 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -517,7 +517,6 @@ serverOnlyFiles = [
"catalog/cursor_manager.cpp",
"catalog/database.cpp",
"catalog/database_holder.cpp",
- "catalog/document_validation.cpp",
"catalog/drop_collection.cpp",
"catalog/drop_database.cpp",
"catalog/drop_indexes.cpp",
@@ -655,6 +654,7 @@ serveronlyLibdeps = [
"$BUILD_DIR/third_party/shim_snappy",
"auth/authmongod",
"catalog/collection_options",
+ "catalog/document_validation",
"catalog/index_key_validate",
"commands/killcursors_common",
"collection_index_usage_tracker",
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index a0b78e4d3b2..90c7a01e72c 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -36,6 +36,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 1007316052b..4f80f19439e 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/security_key.h"
+#include "mongo/db/catalog/document_validation.h"
#include "mongo/db/client.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
@@ -243,12 +244,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() == StringData("system.indexes", StringData::LiteralTag())) {
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.");
}
@@ -258,7 +261,14 @@ Status AuthorizationSession::checkAuthForInsert(const NamespaceString& ns,
str::stream() << "not authorized to create index on " << indexNS.ns());
}
} else {
- if (!isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) {
+ ActionSet required;
+ required.addAction(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());
}
@@ -267,28 +277,35 @@ 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;
+ required.addAction(ActionType::update);
+ StringData operationType = "update";
+
+ 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";
+ }
+
+ 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 da11de0da2d..7b5ce4799fa 100644
--- a/src/mongo/db/auth/authorization_session.h
+++ b/src/mongo/db/auth/authorization_session.h
@@ -150,7 +150,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);
@@ -158,11 +159,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/catalog/SConscript b/src/mongo/db/catalog/SConscript
index 6640eb268f9..2baa7a6b8e6 100644
--- a/src/mongo/db/catalog/SConscript
+++ b/src/mongo/db/catalog/SConscript
@@ -8,6 +8,16 @@ env.CppUnitTest('collection_options_test', ['collection_options_test.cpp'],
LIBDEPS=['collection_options'])
env.Library(
+ target='document_validation',
+ source=[
+ "document_validation.cpp",
+ ],
+ LIBDEPS=[
+ "$BUILD_DIR/mongo/db/service_context",
+ ],
+)
+
+env.Library(
target='index_key_validate',
source=[
"index_key_validate.cpp",
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp
index 65560607e50..4b5b6c527ab 100644
--- a/src/mongo/db/commands.cpp
+++ b/src/mongo/db/commands.cpp
@@ -312,6 +312,12 @@ Status Command::parseCommandCursorOptions(const BSONObj& cmdObj,
return Status::OK();
}
+Status Command::checkAuthForOperation(OperationContext* txn,
+ const std::string& dbname,
+ const BSONObj& cmdObj) {
+ return checkAuthForCommand(txn->getClient(), dbname, cmdObj);
+}
+
Status Command::checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
@@ -341,16 +347,17 @@ void Command::logIfSlow(const Timer& timer, const string& msg) {
}
static Status _checkAuthorizationImpl(Command* c,
- ClientBasic* 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->name << " 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);
@@ -370,16 +377,16 @@ static Status _checkAuthorizationImpl(Command* c,
return Status::OK();
}
-Status Command::_checkAuthorization(Command* c,
- ClientBasic* 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 37550c64131..71ba638117b 100644
--- a/src/mongo/db/commands.h
+++ b/src/mongo/db/commands.h
@@ -199,12 +199,12 @@ public:
}
/**
- * 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(ClientBasic* 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.
@@ -259,18 +259,6 @@ public:
Command(StringData _name, bool webUI = false, StringData oldName = StringData());
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);
- }
-
BSONObj getQuery(const BSONObj& cmdObj) {
if (cmdObj["query"].type() == Object)
return cmdObj["query"].embeddedObject();
@@ -451,19 +439,40 @@ public:
*/
static void registerError(OperationContext* txn, const DBException& exception);
-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,
- ClientBasic* 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(ClientBasic* 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 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);
+ }
};
void runCommands(OperationContext* txn,
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index d36f661e414..b4e3cebf04d 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -48,6 +48,7 @@ env.Library(
'$BUILD_DIR/mongo/db/auth/authorization_manager_global',
'$BUILD_DIR/mongo/db/auth/serverauth',
'$BUILD_DIR/mongo/db/commands',
+ '$BUILD_DIR/mongo/db/commands/apply_ops_cmd_common',
'$BUILD_DIR/mongo/db/commands/test_commands_enabled',
'$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/mongo/db/curop',
@@ -95,6 +96,21 @@ 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.CppUnitTest(
target="index_filter_commands_test",
source=[
diff --git a/src/mongo/db/commands/apply_ops.cpp b/src/mongo/db/commands/apply_ops.cpp
index 12dc70ceb86..04e9117b548 100644
--- a/src/mongo/db/commands/apply_ops.cpp
+++ b/src/mongo/db/commands/apply_ops.cpp
@@ -37,13 +37,12 @@
#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/dbhash.h"
+#include "mongo/db/commands/apply_ops_cmd_common.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/dbdirectclient.h"
@@ -78,12 +77,13 @@ public:
help << "internal (sharding)\n{ applyOps : [ ] , preCondition : [ { ns : ... , q : ... , "
"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) final {
+ return checkAuthForApplyOpsCommand(txn, dbname, cmdObj);
}
+
virtual bool run(OperationContext* txn,
const string& dbname,
BSONObj& cmdObj,
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..045570abf8a
--- /dev/null
+++ b/src/mongo/db/commands/apply_ops_cmd_common.cpp
@@ -0,0 +1,165 @@
+/**
+ * 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") {
+ // 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") {
+ 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") {
+ return authSession->checkAuthForInsert(txn, ns, o);
+ } else if (opType == "u") {
+ 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") {
+ return authSession->checkAuthForDelete(txn, ns, o);
+ } else if (opType == "db") {
+ // 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/explain_cmd.cpp b/src/mongo/db/commands/explain_cmd.cpp
index 2af6f18fed5..ffae7056bc7 100644
--- a/src/mongo/db/commands/explain_cmd.cpp
+++ b/src/mongo/db/commands/explain_cmd.cpp
@@ -88,9 +88,9 @@ public:
* the command that you are explaining. The auth check is performed recursively
* on the nested command.
*/
- virtual Status checkAuthForCommand(ClientBasic* 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");
}
@@ -104,7 +104,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/dbcommands.cpp b/src/mongo/db/dbcommands.cpp
index f2b87b6c399..e65da17dd07 100644
--- a/src/mongo/db/dbcommands.cpp
+++ b/src/mongo/db/dbcommands.cpp
@@ -1230,8 +1230,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/instance.cpp b/src/mongo/db/instance.cpp
index 4f393385127..86576565532 100644
--- a/src/mongo/db/instance.cpp
+++ b/src/mongo/db/instance.cpp
@@ -669,8 +669,8 @@ void receivedUpdate(OperationContext* txn, const NamespaceString& nsString, Mess
op.setQuery_inlock(query);
}
- Status status =
- AuthorizationSession::get(client)->checkAuthForUpdate(nsString, query, toupdate, upsert);
+ Status status = AuthorizationSession::get(client)
+ ->checkAuthForUpdate(txn, nsString, query, toupdate, upsert);
audit::logUpdateAuthzCheck(client, nsString, query, toupdate, upsert, multi, status.code());
uassertStatusOK(status);
@@ -818,7 +818,7 @@ void receivedDelete(OperationContext* txn, const NamespaceString& nsString, Mess
op.setNS_inlock(nsString.ns());
}
- Status status = AuthorizationSession::get(client)->checkAuthForDelete(nsString, pattern);
+ Status status = AuthorizationSession::get(client)->checkAuthForDelete(txn, nsString, pattern);
audit::logDeleteAuthzCheck(client, nsString, pattern, status.code());
uassertStatusOK(status);
@@ -1181,7 +1181,7 @@ void receivedInsert(OperationContext* txn, const NamespaceString& nsString, Mess
// Check auth for insert (also handles checking if this is an index build and checks
// for the proper privileges in that case).
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);
}
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 3a988888202..5400114f85d 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -65,6 +65,7 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/client/parallel',
+ '$BUILD_DIR/mongo/db/commands/apply_ops_cmd_common',
'$BUILD_DIR/mongo/db/pipeline/pipeline',
'$BUILD_DIR/mongo/db/commands/killcursors_common',
'$BUILD_DIR/mongo/s/coreshard',
diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp
index a48076dc93a..0e1353bd9fc 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(ClientBasic* 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 6737fe47674..5d7ea13a58d 100644
--- a/src/mongo/s/commands/commands_public.cpp
+++ b/src/mongo/s/commands/commands_public.cpp
@@ -39,6 +39,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"
@@ -1371,11 +1372,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 run(OperationContext* txn,
const string& dbName,
diff --git a/src/mongo/s/s_only.cpp b/src/mongo/s/s_only.cpp
index 2322aa67d3d..3cf43adb15c 100644
--- a/src/mongo/s/s_only.cpp
+++ b/src/mongo/s/s_only.cpp
@@ -110,7 +110,7 @@ void Command::execCommandClientBasic(OperationContext* txn,
return;
}
- Status status = _checkAuthorization(c, &client, dbname, cmdObj);
+ Status status = checkAuthorization(c, txn, dbname, cmdObj);
if (!status.isOK()) {
appendCommandStatus(result, status);
return;