StatusWith<StringData> ClientMetadata::parseApplicationDocument(const BSONObj& doc) { BSONObjIterator i(doc); while (i.more()) { BSONElement e = i.next(); StringData name = e.fieldNameStringData(); // Name is the only required field, and any other fields are simply ignored. if (name == kName) { if (e.type() != String) { return { ErrorCodes::TypeMismatch, str::stream() << "The '" << kApplication << "." << kName << "' field must be a string in the client metadata document"}; } StringData value = e.checkAndGetStringData(); if (value.size() > kMaxApplicationNameByteLength) { return {ErrorCodes::ClientMetadataAppNameTooLarge, str::stream() << "The '" << kApplication << "." << kName << "' field must be less then or equal to " << kMaxApplicationNameByteLength << " bytes in the client metadata document"}; } return {std::move(value)}; } } return {StringData()}; }
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(); }