1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
32 
33 #include "mongo/platform/basic.h"
34 
35 #include "mongo/db/commands.h"
36 
37 #include <string>
38 #include <vector>
39 
40 #include "mongo/bson/mutable/document.h"
41 #include "mongo/bson/timestamp.h"
42 #include "mongo/db/audit.h"
43 #include "mongo/db/auth/action_set.h"
44 #include "mongo/db/auth/action_type.h"
45 #include "mongo/db/auth/authorization_manager.h"
46 #include "mongo/db/auth/authorization_session.h"
47 #include "mongo/db/auth/privilege.h"
48 #include "mongo/db/catalog/uuid_catalog.h"
49 #include "mongo/db/client.h"
50 #include "mongo/db/curop.h"
51 #include "mongo/db/jsobj.h"
52 #include "mongo/db/namespace_string.h"
53 #include "mongo/db/server_parameters.h"
54 #include "mongo/rpc/write_concern_error_detail.h"
55 #include "mongo/s/stale_exception.h"
56 #include "mongo/util/log.h"
57 
58 namespace mongo {
59 
60 using std::string;
61 using std::stringstream;
62 
63 using logger::LogComponent;
64 
65 Command::CommandMap* Command::_commandsByBestName = nullptr;
66 Command::CommandMap* Command::_commands = nullptr;
67 
68 Counter64 Command::unknownCommands;
69 static ServerStatusMetricField<Counter64> displayUnknownCommands("commands.<UNKNOWN>",
70                                                                  &Command::unknownCommands);
71 
72 namespace {
73 
74 ExportedServerParameter<bool, ServerParameterType::kStartupOnly> testCommandsParameter(
75     ServerParameterSet::getGlobal(), "enableTestCommands", &Command::testCommandsEnabled);
76 
77 const char kWriteConcernField[] = "writeConcern";
78 const WriteConcernOptions kMajorityWriteConcern(
79     WriteConcernOptions::kMajority,
80     // Note: Even though we're setting UNSET here, kMajority implies JOURNAL if journaling is
81     // supported by the mongod.
82     WriteConcernOptions::SyncMode::UNSET,
83     WriteConcernOptions::kWriteConcernTimeoutUserCommand);
84 
85 }  // namespace
86 
87 Command::~Command() = default;
88 
appendPassthroughFields(const BSONObj & cmdObjWithPassthroughFields,const BSONObj & request)89 BSONObj Command::appendPassthroughFields(const BSONObj& cmdObjWithPassthroughFields,
90                                          const BSONObj& request) {
91     BSONObjBuilder b;
92     b.appendElements(request);
93     for (const auto& elem :
94          Command::filterCommandRequestForPassthrough(cmdObjWithPassthroughFields)) {
95         const auto name = elem.fieldNameStringData();
96         if (Command::isGenericArgument(name) && !request.hasField(name)) {
97             b.append(elem);
98         }
99     }
100     return b.obj();
101 }
102 
appendMajorityWriteConcern(const BSONObj & cmdObj)103 BSONObj Command::appendMajorityWriteConcern(const BSONObj& cmdObj) {
104     if (cmdObj.hasField(kWriteConcernField)) {
105         return cmdObj;
106     }
107     BSONObjBuilder cmdObjWithWriteConcern;
108     cmdObjWithWriteConcern.appendElementsUnique(cmdObj);
109     cmdObjWithWriteConcern.append(kWriteConcernField, kMajorityWriteConcern.toBSON());
110     return cmdObjWithWriteConcern.obj();
111 }
112 
parseNsFullyQualified(const string & dbname,const BSONObj & cmdObj)113 string Command::parseNsFullyQualified(const string& dbname, const BSONObj& cmdObj) {
114     BSONElement first = cmdObj.firstElement();
115     uassert(ErrorCodes::BadValue,
116             str::stream() << "collection name has invalid type " << typeName(first.type()),
117             first.canonicalType() == canonicalizeBSONType(mongo::String));
118     const NamespaceString nss(first.valueStringData());
119     uassert(ErrorCodes::InvalidNamespace,
120             str::stream() << "Invalid namespace specified '" << nss.ns() << "'",
121             nss.isValid());
122     return nss.ns();
123 }
124 
parseNsCollectionRequired(const string & dbname,const BSONObj & cmdObj)125 NamespaceString Command::parseNsCollectionRequired(const string& dbname, const BSONObj& cmdObj) {
126     // Accepts both BSON String and Symbol for collection name per SERVER-16260
127     // TODO(kangas) remove Symbol support in MongoDB 3.0 after Ruby driver audit
128     BSONElement first = cmdObj.firstElement();
129     const bool isUUID = (first.canonicalType() == canonicalizeBSONType(mongo::BinData) &&
130                          first.binDataType() == BinDataType::newUUID);
131     uassert(ErrorCodes::InvalidNamespace,
132             str::stream() << "Collection name must be provided. UUID is not valid in this "
133                           << "context",
134             !isUUID);
135     uassert(ErrorCodes::InvalidNamespace,
136             str::stream() << "collection name has invalid type " << typeName(first.type()),
137             first.canonicalType() == canonicalizeBSONType(mongo::String));
138     const NamespaceString nss(dbname, first.valueStringData());
139     uassert(ErrorCodes::InvalidNamespace,
140             str::stream() << "Invalid namespace specified '" << nss.ns() << "'",
141             nss.isValid());
142     return nss;
143 }
144 
parseNsOrUUID(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj)145 NamespaceString Command::parseNsOrUUID(OperationContext* opCtx,
146                                        const string& dbname,
147                                        const BSONObj& cmdObj) {
148     BSONElement first = cmdObj.firstElement();
149     if (first.type() == BinData && first.binDataType() == BinDataType::newUUID) {
150         UUIDCatalog& catalog = UUIDCatalog::get(opCtx);
151         UUID uuid = uassertStatusOK(UUID::parse(first));
152         NamespaceString nss = catalog.lookupNSSByUUID(uuid);
153         uassert(ErrorCodes::NamespaceNotFound,
154                 str::stream() << "UUID " << uuid << " specified in "
155                               << cmdObj.firstElement().fieldNameStringData()
156                               << " command not found in "
157                               << dbname
158                               << " database",
159                 nss.isValid() && nss.db() == dbname);
160 
161         return nss;
162     } else {
163         // Ensure collection identifier is not a Command
164         const NamespaceString nss(parseNsCollectionRequired(dbname, cmdObj));
165         uassert(ErrorCodes::InvalidNamespace,
166                 str::stream() << "Invalid collection name specified '" << nss.ns() << "'",
167                 nss.isNormal());
168         return nss;
169     }
170 }
171 
parseNs(const string & dbname,const BSONObj & cmdObj) const172 string Command::parseNs(const string& dbname, const BSONObj& cmdObj) const {
173     BSONElement first = cmdObj.firstElement();
174     if (first.type() != mongo::String)
175         return dbname;
176 
177     return str::stream() << dbname << '.' << cmdObj.firstElement().valueStringData();
178 }
179 
parseResourcePattern(const std::string & dbname,const BSONObj & cmdObj) const180 ResourcePattern Command::parseResourcePattern(const std::string& dbname,
181                                               const BSONObj& cmdObj) const {
182     const std::string ns = parseNs(dbname, cmdObj);
183     if (!NamespaceString::validCollectionComponent(ns)) {
184         return ResourcePattern::forDatabaseName(ns);
185     }
186     return ResourcePattern::forExactNamespace(NamespaceString(ns));
187 }
188 
Command(StringData name,std::vector<StringData> aliases)189 Command::Command(StringData name, std::vector<StringData> aliases)
190     : _name(name.toString()),
191       _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted),
192       _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) {
193     // register ourself.
194     if (_commands == 0)
195         _commands = new CommandMap();
196     if (_commandsByBestName == 0)
197         _commandsByBestName = new CommandMap();
198     Command*& c = (*_commands)[name];
199     if (c)
200         log() << "warning: 2 commands with name: " << _name;
201     c = this;
202     (*_commandsByBestName)[name] = this;
203 
204     for (auto key : aliases) {
205         if (key.empty()) {
206             continue;
207         }
208         (*_commands)[key.toString()] = this;
209     }
210 }
211 
help(stringstream & help) const212 void Command::help(stringstream& help) const {
213     help << "no help defined";
214 }
215 
explain(OperationContext * opCtx,const string & dbname,const BSONObj & cmdObj,ExplainOptions::Verbosity verbosity,BSONObjBuilder * out) const216 Status Command::explain(OperationContext* opCtx,
217                         const string& dbname,
218                         const BSONObj& cmdObj,
219                         ExplainOptions::Verbosity verbosity,
220                         BSONObjBuilder* out) const {
221     return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()};
222 }
223 
runCommandDirectly(OperationContext * opCtx,const OpMsgRequest & request)224 BSONObj Command::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) {
225     auto command = Command::findCommand(request.getCommandName());
226     invariant(command);
227 
228     BSONObjBuilder out;
229     try {
230         bool ok = command->publicRun(opCtx, request, out);
231         appendCommandStatus(out, ok);
232     } catch (const StaleConfigException&) {
233         // These exceptions are intended to be handled at a higher level and cannot losslessly
234         // round-trip through Status.
235         throw;
236     } catch (const DBException& ex) {
237         out.resetToEmpty();
238         appendCommandStatus(out, ex.toStatus());
239     }
240     return out.obj();
241 }
242 
findCommand(StringData name)243 Command* Command::findCommand(StringData name) {
244     CommandMap::const_iterator i = _commands->find(name);
245     if (i == _commands->end())
246         return 0;
247     return i->second;
248 }
249 
appendCommandStatus(BSONObjBuilder & result,const Status & status)250 bool Command::appendCommandStatus(BSONObjBuilder& result, const Status& status) {
251     appendCommandStatus(result, status.isOK(), status.reason());
252     BSONObj tmp = result.asTempObj();
253     if (!status.isOK() && !tmp.hasField("code")) {
254         result.append("code", status.code());
255         result.append("codeName", ErrorCodes::errorString(status.code()));
256     }
257     return status.isOK();
258 }
259 
appendCommandStatus(BSONObjBuilder & result,bool ok,const std::string & errmsg)260 void Command::appendCommandStatus(BSONObjBuilder& result, bool ok, const std::string& errmsg) {
261     BSONObj tmp = result.asTempObj();
262     bool have_ok = tmp.hasField("ok");
263     bool need_errmsg = !ok && !tmp.hasField("errmsg");
264 
265     if (!have_ok)
266         result.append("ok", ok ? 1.0 : 0.0);
267 
268     if (need_errmsg) {
269         result.append("errmsg", errmsg);
270     }
271 }
272 
appendCommandWCStatus(BSONObjBuilder & result,const Status & awaitReplicationStatus,const WriteConcernResult & wcResult)273 void Command::appendCommandWCStatus(BSONObjBuilder& result,
274                                     const Status& awaitReplicationStatus,
275                                     const WriteConcernResult& wcResult) {
276     if (!awaitReplicationStatus.isOK() && !result.hasField("writeConcernError")) {
277         WriteConcernErrorDetail wcError;
278         wcError.setErrCode(awaitReplicationStatus.code());
279         wcError.setErrMessage(awaitReplicationStatus.reason());
280         if (wcResult.wTimedOut) {
281             wcError.setErrInfo(BSON("wtimeout" << true));
282         }
283         result.append("writeConcernError", wcError.toBSON());
284     }
285 }
286 
checkAuthForRequest(OperationContext * opCtx,const OpMsgRequest & request)287 Status BasicCommand::checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) {
288     uassertNoDocumentSequences(request);
289     return checkAuthForOperation(opCtx, request.getDatabase().toString(), request.body);
290 }
291 
checkAuthForOperation(OperationContext * opCtx,const std::string & dbname,const BSONObj & cmdObj)292 Status BasicCommand::checkAuthForOperation(OperationContext* opCtx,
293                                            const std::string& dbname,
294                                            const BSONObj& cmdObj) {
295     return checkAuthForCommand(opCtx->getClient(), dbname, cmdObj);
296 }
297 
checkAuthForCommand(Client * client,const std::string & dbname,const BSONObj & cmdObj)298 Status BasicCommand::checkAuthForCommand(Client* client,
299                                          const std::string& dbname,
300                                          const BSONObj& cmdObj) {
301     std::vector<Privilege> privileges;
302     this->addRequiredPrivileges(dbname, cmdObj, &privileges);
303     if (AuthorizationSession::get(client)->isAuthorizedForPrivileges(privileges))
304         return Status::OK();
305     return Status(ErrorCodes::Unauthorized, "unauthorized");
306 }
307 
redactForLogging(mutablebson::Document * cmdObj)308 void Command::redactForLogging(mutablebson::Document* cmdObj) {}
309 
getRedactedCopyForLogging(const BSONObj & cmdObj)310 BSONObj Command::getRedactedCopyForLogging(const BSONObj& cmdObj) {
311     namespace mmb = mutablebson;
312     mmb::Document cmdToLog(cmdObj, mmb::Document::kInPlaceDisabled);
313     redactForLogging(&cmdToLog);
314     BSONObjBuilder bob;
315     cmdToLog.writeTo(&bob);
316     return bob.obj();
317 }
318 
_checkAuthorizationImpl(Command * c,OperationContext * opCtx,const OpMsgRequest & request)319 static Status _checkAuthorizationImpl(Command* c,
320                                       OperationContext* opCtx,
321                                       const OpMsgRequest& request) {
322     namespace mmb = mutablebson;
323     auto client = opCtx->getClient();
324     auto dbname = request.getDatabase();
325     if (c->adminOnly() && dbname != "admin") {
326         return Status(ErrorCodes::Unauthorized,
327                       str::stream() << c->getName()
328                                     << " may only be run against the admin database.");
329     }
330     if (AuthorizationSession::get(client)->getAuthorizationManager().isAuthEnabled()) {
331         Status status = c->checkAuthForRequest(opCtx, request);
332         if (status == ErrorCodes::Unauthorized) {
333             mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled);
334             c->redactForLogging(&cmdToLog);
335             return Status(ErrorCodes::Unauthorized,
336                           str::stream() << "not authorized on " << dbname << " to execute command "
337                                         << redact(cmdToLog.getObject()));
338         }
339         if (!status.isOK()) {
340             return status;
341         }
342     } else if (c->adminOnly() && c->localHostOnlyIfNoAuth() &&
343                !client->getIsLocalHostConnection()) {
344         return Status(ErrorCodes::Unauthorized,
345                       str::stream() << c->getName()
346                                     << " must run from localhost when running db without auth");
347     }
348     return Status::OK();
349 }
350 
checkAuthorization(Command * c,OperationContext * opCtx,const OpMsgRequest & request)351 Status Command::checkAuthorization(Command* c,
352                                    OperationContext* opCtx,
353                                    const OpMsgRequest& request) {
354     Status status = _checkAuthorizationImpl(c, opCtx, request);
355     if (!status.isOK()) {
356         log(LogComponent::kAccessControl) << status;
357     }
358     audit::logCommandAuthzCheck(opCtx->getClient(), request, c, status.code());
359     return status;
360 }
361 
publicRun(OperationContext * opCtx,const OpMsgRequest & request,BSONObjBuilder & result)362 bool Command::publicRun(OperationContext* opCtx,
363                         const OpMsgRequest& request,
364                         BSONObjBuilder& result) {
365     try {
366         return enhancedRun(opCtx, request, result);
367     } catch (const DBException& e) {
368         if (e.code() == ErrorCodes::Unauthorized) {
369             audit::logCommandAuthzCheck(
370                 opCtx->getClient(), request, this, ErrorCodes::Unauthorized);
371         }
372         throw;
373     }
374 }
375 
isHelpRequest(const BSONElement & helpElem)376 bool Command::isHelpRequest(const BSONElement& helpElem) {
377     return !helpElem.eoo() && helpElem.trueValue();
378 }
379 
380 const char Command::kHelpFieldName[] = "help";
381 
generateHelpResponse(OperationContext * opCtx,rpc::ReplyBuilderInterface * replyBuilder,const Command & command)382 void Command::generateHelpResponse(OperationContext* opCtx,
383                                    rpc::ReplyBuilderInterface* replyBuilder,
384                                    const Command& command) {
385     std::stringstream ss;
386     BSONObjBuilder helpBuilder;
387     ss << "help for: " << command.getName() << " ";
388     command.help(ss);
389     helpBuilder.append("help", ss.str());
390 
391     replyBuilder->setCommandReply(helpBuilder.obj());
392     replyBuilder->setMetadata(rpc::makeEmptyMetadata());
393 }
394 
hasAlias(const StringData & alias) const395 bool Command::hasAlias(const StringData& alias) const {
396     return findCommand(alias) == this;
397 }
398 
399 namespace {
400 const stdx::unordered_set<std::string> userManagementCommands{"createUser",
401                                                               "updateUser",
402                                                               "dropUser",
403                                                               "dropAllUsersFromDatabase",
404                                                               "grantRolesToUser",
405                                                               "revokeRolesFromUser",
406                                                               "createRole",
407                                                               "updateRole",
408                                                               "dropRole",
409                                                               "dropAllRolesFromDatabase",
410                                                               "grantPrivilegesToRole",
411                                                               "revokePrivilegesFromRole",
412                                                               "grantRolesToRole",
413                                                               "revokeRolesFromRole",
414                                                               "_mergeAuthzCollections",
415                                                               "authSchemaUpgrade"};
416 }  // namespace
417 
isUserManagementCommand(const std::string & name)418 bool Command::isUserManagementCommand(const std::string& name) {
419     return userManagementCommands.count(name);
420 }
421 
uassertNoDocumentSequences(const OpMsgRequest & request)422 void BasicCommand::uassertNoDocumentSequences(const OpMsgRequest& request) {
423     uassert(40472,
424             str::stream() << "The " << getName() << " command does not support document sequences.",
425             request.sequences.empty());
426 }
427 
enhancedRun(OperationContext * opCtx,const OpMsgRequest & request,BSONObjBuilder & result)428 bool BasicCommand::enhancedRun(OperationContext* opCtx,
429                                const OpMsgRequest& request,
430                                BSONObjBuilder& result) {
431     uassertNoDocumentSequences(request);
432     return run(opCtx, request.getDatabase().toString(), request.body, result);
433 }
434 
run(OperationContext * opCtx,const std::string & db,const BSONObj & cmdObj,BSONObjBuilder & result)435 bool ErrmsgCommandDeprecated::run(OperationContext* opCtx,
436                                   const std::string& db,
437                                   const BSONObj& cmdObj,
438                                   BSONObjBuilder& result) {
439     std::string errmsg;
440     auto ok = errmsgRun(opCtx, db, cmdObj, errmsg, result);
441     if (!errmsg.empty()) {
442         appendCommandStatus(result, ok, errmsg);
443     }
444     return ok;
445 }
446 
filterCommandRequestForPassthrough(const BSONObj & cmdObj)447 BSONObj Command::filterCommandRequestForPassthrough(const BSONObj& cmdObj) {
448     BSONObjBuilder bob;
449     for (auto elem : cmdObj) {
450         const auto name = elem.fieldNameStringData();
451         if (name == "$readPreference") {
452             BSONObjBuilder(bob.subobjStart("$queryOptions")).append(elem);
453         } else if (!Command::isGenericArgument(name) ||  //
454                    name == "$queryOptions" ||            //
455                    name == "maxTimeMS" ||                //
456                    name == "readConcern" ||              //
457                    name == "writeConcern" ||             //
458                    name == "lsid" ||                     //
459                    name == "txnNumber") {
460             // This is the whitelist of generic arguments that commands can be trusted to blindly
461             // forward to the shards.
462             bob.append(elem);
463         }
464     }
465     return bob.obj();
466 }
467 
filterCommandReplyForPassthrough(const BSONObj & cmdObj,BSONObjBuilder * output)468 void Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj, BSONObjBuilder* output) {
469     for (auto elem : cmdObj) {
470         const auto name = elem.fieldNameStringData();
471         if (name == "$configServerState" ||  //
472             name == "$gleStats" ||           //
473             name == "$clusterTime" ||        //
474             name == "$oplogQueryData" ||     //
475             name == "$replData" ||           //
476             name == "operationTime") {
477             continue;
478         }
479         output->append(elem);
480     }
481 }
482 
filterCommandReplyForPassthrough(const BSONObj & cmdObj)483 BSONObj Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj) {
484     BSONObjBuilder bob;
485     filterCommandReplyForPassthrough(cmdObj, &bob);
486     return bob.obj();
487 }
488 
489 }  // namespace mongo
490