ApplyOpsValidity validateApplyOpsCommand(const BSONObj& cmdObj) { const size_t maxApplyOpsDepth = 10; std::stack<std::pair<size_t, BSONObj>> toCheck; auto operationContainsApplyOps = [](const BSONObj& opObj) { BSONElement opTypeElem = opObj["op"]; checkBSONType(BSONType::String, opTypeElem); const StringData opType = opTypeElem.checkAndGetStringData(); if (opType == "c"_sd) { BSONElement oElem = opObj["o"]; checkBSONType(BSONType::Object, oElem); BSONObj o = oElem.Obj(); if (o.firstElement().fieldNameStringData() == "applyOps"_sd) { return true; } } return false; }; // Insert the top level applyOps command into the stack. toCheck.emplace(std::make_pair(0, cmdObj)); while (!toCheck.empty()) { std::pair<size_t, BSONObj> item = toCheck.top(); toCheck.pop(); checkBSONType(BSONType::Array, item.second.firstElement()); // Check if the applyOps command is empty. This is probably not something that should // happen, so require a superuser to do this. if (item.second.firstElement().Array().empty()) { return ApplyOpsValidity::kNeedsSuperuser; } // For each applyOps command, iterate the ops. for (BSONElement element : item.second.firstElement().Array()) { checkBSONType(BSONType::Object, element); BSONObj elementObj = element.Obj(); // If the op itself contains an applyOps... if (operationContainsApplyOps(elementObj)) { // And we've recursed too far, then bail out. uassert(ErrorCodes::FailedToParse, "Too many nested applyOps", item.first < maxApplyOpsDepth); // Otherwise, if the op contains an applyOps, but we haven't recursed too far: // extract the applyOps command, and insert it into the stack. checkBSONType(BSONType::Object, elementObj["o"]); BSONObj oObj = elementObj["o"].Obj(); toCheck.emplace(std::make_pair(item.first + 1, std::move(oObj))); } } } return ApplyOpsValidity::kOk; }
Status checkAuthForApplyOpsCommand(OperationContext* txn, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authSession = AuthorizationSession::get(txn->getClient()); ApplyOpsValidity validity = validateApplyOpsCommand(cmdObj); if (validity == ApplyOpsValidity::kNeedsSuperuser) { std::vector<Privilege> universalPrivileges; RoleGraph::generateUniversalPrivileges(&universalPrivileges); if (!authSession->isAuthorizedForPrivileges(universalPrivileges)) { return Status(ErrorCodes::Unauthorized, "Unauthorized"); } return Status::OK(); } fassert(40314, validity == ApplyOpsValidity::kOk); 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(); }
DeleteOp parseDeleteCommand(StringData dbName, const BSONObj& cmd) { BSONElement deletes; DeleteOp op; parseWriteCommand(dbName, cmd, "deletes", &deletes, &op); checkBSONType(Array, deletes); for (auto doc : deletes.Obj()) { checkTypeInArray(Object, doc, deletes); op.deletes.emplace_back(); auto& del = op.deletes.back(); // delete is a reserved word. bool haveQ = false; bool haveLimit = false; for (auto field : doc.Obj()) { const StringData fieldName = field.fieldNameStringData(); if (fieldName == "q") { haveQ = true; checkBSONType(Object, field); del.query = field.Obj(); } else if (fieldName == "collation") { checkBSONType(Object, field); del.collation = field.Obj(); } else if (fieldName == "limit") { haveLimit = true; uassert(ErrorCodes::TypeMismatch, str::stream() << "The limit field in delete objects must be a number. Got a " << typeName(field.type()), field.isNumber()); // Using a double to avoid throwing away illegal fractional portion. Don't want to // accept 0.5 here. const double limit = field.numberDouble(); uassert(ErrorCodes::FailedToParse, str::stream() << "The limit field in delete objects must be 0 or 1. Got " << limit, limit == 0 || limit == 1); del.multi = (limit == 0); } else { uasserted(ErrorCodes::FailedToParse, str::stream() << "Unrecognized field in delete operation: " << fieldName); } } uassert(ErrorCodes::FailedToParse, "The 'q' field is required for all deletes", haveQ); uassert( ErrorCodes::FailedToParse, "The 'limit' field is required for all deletes", haveLimit); } checkOpCountForCommand(op.deletes.size()); return op; }
UpdateOp parseUpdateCommand(StringData dbName, const BSONObj& cmd) { BSONElement updates; UpdateOp op; parseWriteCommand(dbName, cmd, "updates", &updates, &op); checkBSONType(Array, updates); for (auto doc : updates.Obj()) { checkTypeInArray(Object, doc, updates); op.updates.emplace_back(); auto& update = op.updates.back(); bool haveQ = false; bool haveU = false; for (auto field : doc.Obj()) { const StringData fieldName = field.fieldNameStringData(); if (fieldName == "q") { haveQ = true; checkBSONType(Object, field); update.query = field.Obj(); } else if (fieldName == "u") { haveU = true; checkBSONType(Object, field); update.update = field.Obj(); } else if (fieldName == "collation") { checkBSONType(Object, field); update.collation = field.Obj(); } else if (fieldName == "arrayFilters") { checkBSONType(Array, field); for (auto arrayFilter : field.Obj()) { checkBSONType(Object, arrayFilter); update.arrayFilters.push_back(arrayFilter.Obj()); } } else if (fieldName == "multi") { checkBSONType(Bool, field); update.multi = field.Bool(); } else if (fieldName == "upsert") { checkBSONType(Bool, field); update.upsert = field.Bool(); } else { uasserted(ErrorCodes::FailedToParse, str::stream() << "Unrecognized field in update operation: " << fieldName); } } uassert(ErrorCodes::FailedToParse, "The 'q' field is required for all updates", haveQ); uassert(ErrorCodes::FailedToParse, "The 'u' field is required for all updates", haveU); } checkOpCountForCommand(op.updates.size()); return op; }
InsertOp parseInsertCommand(StringData dbName, const BSONObj& cmd) { BSONElement documents; InsertOp op; parseWriteCommand(dbName, cmd, "documents", &documents, &op); checkBSONType(Array, documents); for (auto doc : documents.Obj()) { checkTypeInArray(Object, doc, documents); op.documents.push_back(doc.Obj()); } checkOpCountForCommand(op.documents.size()); if (op.ns.isSystemDotIndexes()) { // This is only for consistency with sharding. uassert(ErrorCodes::InvalidLength, "Insert commands to system.indexes are limited to a single insert", op.documents.size() == 1); } return op; }