Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx,
                                                const NamespaceString& collectionName,
                                                const BSONObj& query,
                                                const BSONObj& updatePattern,
                                                bool upsert,
                                                const BSONObj& writeConcern) {
    namespace mmb = mutablebson;
    const CollatorInterface* collator = nullptr;
    boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator));
    UpdateDriver driver(std::move(expCtx));
    std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
    driver.parse(updatePattern, arrayFilters);

    BSONObjCollection::iterator iter;
    Status status = _findOneIter(opCtx, collectionName, query, &iter);
    mmb::Document document;
    if (status.isOK()) {
        document.reset(*iter, mmb::Document::kInPlaceDisabled);
        const bool validateForStorage = false;
        const FieldRefSet emptyImmutablePaths;
        const bool isInsert = false;
        BSONObj logObj;
        status = driver.update(
            StringData(), &document, validateForStorage, emptyImmutablePaths, isInsert, &logObj);
        if (!status.isOK())
            return status;
        BSONObj newObj = document.getObject().copy();
        *iter = newObj;
        BSONObj idQuery = newObj["_id"_sd].Obj();

        if (_authzManager) {
            _authzManager->logOp(opCtx, "u", collectionName, logObj, &idQuery);
        }

        return Status::OK();
    } else if (status == ErrorCodes::NoMatchingDocument && upsert) {
        if (query.hasField("_id")) {
            document.root().appendElement(query["_id"]).transitional_ignore();
        }
        const FieldRef idFieldRef("_id");
        FieldRefSet immutablePaths;
        invariant(immutablePaths.insert(&idFieldRef));
        status = driver.populateDocumentWithQueryFields(opCtx, query, immutablePaths, document);
        if (!status.isOK()) {
            return status;
        }

        const bool validateForStorage = false;
        const FieldRefSet emptyImmutablePaths;
        const bool isInsert = false;
        status = driver.update(
            StringData(), &document, validateForStorage, emptyImmutablePaths, isInsert);
        if (!status.isOK()) {
            return status;
        }
        return insert(opCtx, collectionName, document.getObject(), writeConcern);
    } else {
        return status;
    }
}
Example #2
0
Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery& query,
                                                     const vector<FieldRef*>* immutablePathsPtr,
                                                     mutablebson::Document& doc) const {
    EqualityMatches equalities;
    Status status = Status::OK();

    if (isDocReplacement()) {
        FieldRefSet pathsToExtract;

        // TODO: Refactor update logic, make _id just another immutable field
        static const FieldRef idPath("_id");
        static const vector<FieldRef*> emptyImmutablePaths;
        const vector<FieldRef*>& immutablePaths =
            immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths;

        pathsToExtract.fillFrom(immutablePaths);
        pathsToExtract.insert(&idPath);

        // Extract only immutable fields from replacement-style
        status =
            pathsupport::extractFullEqualityMatches(*query.root(), pathsToExtract, &equalities);
    } else {
        // Extract all fields from op-style
        status = pathsupport::extractEqualityMatches(*query.root(), &equalities);
    }

    if (!status.isOK())
        return status;

    status = pathsupport::addEqualitiesToDoc(equalities, &doc);
    return status;
}
Example #3
0
/**
 * Updates roleGraph for an update-type oplog operation on admin.system.roles.
 *
 * Treats all updates as upserts.
 */
Status handleOplogUpdate(OperationContext* opCtx,
                         RoleGraph* roleGraph,
                         const BSONObj& updatePattern,
                         const BSONObj& queryPattern) {
    RoleName roleToUpdate;
    Status status = getRoleNameFromIdField(queryPattern["_id"], &roleToUpdate);
    if (!status.isOK())
        return status;

    boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, nullptr));
    UpdateDriver driver(std::move(expCtx));
    driver.setFromOplogApplication(true);

    // Oplog updates do not have array filters.
    std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFilters;
    driver.parse(updatePattern, arrayFilters);

    mutablebson::Document roleDocument;
    status = RoleGraph::getBSONForRole(roleGraph, roleToUpdate, roleDocument.root());
    if (status == ErrorCodes::RoleNotFound) {
        // The query pattern will only contain _id, no other immutable fields are present
        const FieldRef idFieldRef("_id");
        FieldRefSet immutablePaths;
        invariant(immutablePaths.insert(&idFieldRef));
        status = driver.populateDocumentWithQueryFields(
            opCtx, queryPattern, immutablePaths, roleDocument);
    }
    if (!status.isOK())
        return status;

    const bool validateForStorage = false;
    const FieldRefSet emptyImmutablePaths;
    status = driver.update(StringData(), &roleDocument, validateForStorage, emptyImmutablePaths);
    if (!status.isOK())
        return status;

    // Now use the updated document to totally replace the role in the graph!
    RoleInfo role;
    status = parseRoleFromDocument(roleDocument.getObject(), &role);
    if (!status.isOK())
        return status;
    status = roleGraph->replaceRole(role.name, role.roles, role.privileges, role.restrictions);

    return status;
}
Status AuthzManagerExternalStateMock::updateOne(OperationContext* opCtx,
                                                const NamespaceString& collectionName,
                                                const BSONObj& query,
                                                const BSONObj& updatePattern,
                                                bool upsert,
                                                const BSONObj& writeConcern) {
    namespace mmb = mutablebson;
    UpdateDriver::Options updateOptions;
    UpdateDriver driver(updateOptions);
    std::map<StringData, std::unique_ptr<ArrayFilter>> arrayFilters;
    Status status = driver.parse(updatePattern, arrayFilters);
    if (!status.isOK())
        return status;

    BSONObjCollection::iterator iter;
    status = _findOneIter(collectionName, query, &iter);
    mmb::Document document;
    if (status.isOK()) {
        document.reset(*iter, mmb::Document::kInPlaceDisabled);
        const BSONObj emptyOriginal;
        const bool validateForStorage = false;
        const FieldRefSet emptyImmutablePaths;
        BSONObj logObj;
        status = driver.update(StringData(),
                               emptyOriginal,
                               &document,
                               validateForStorage,
                               emptyImmutablePaths,
                               &logObj);
        if (!status.isOK())
            return status;
        BSONObj newObj = document.getObject().copy();
        *iter = newObj;
        BSONObj idQuery = driver.makeOplogEntryQuery(newObj, false);

        if (_authzManager) {
            _authzManager->logOp(opCtx, "u", collectionName, logObj, &idQuery);
        }

        return Status::OK();
    } else if (status == ErrorCodes::NoMatchingDocument && upsert) {
        if (query.hasField("_id")) {
            document.root().appendElement(query["_id"]).transitional_ignore();
        }
        const FieldRef idFieldRef("_id");
        FieldRefSet immutablePaths;
        invariant(immutablePaths.insert(&idFieldRef));
        status = driver.populateDocumentWithQueryFields(opCtx, query, immutablePaths, document);
        if (!status.isOK()) {
            return status;
        }

        // The original document can be empty because it is only needed for validation of immutable
        // paths.
        const BSONObj emptyOriginal;
        const bool validateForStorage = false;
        const FieldRefSet emptyImmutablePaths;
        status = driver.update(
            StringData(), emptyOriginal, &document, validateForStorage, emptyImmutablePaths);
        if (!status.isOK()) {
            return status;
        }
        return insert(opCtx, collectionName, document.getObject(), writeConcern);
    } else {
        return status;
    }
}
Example #5
0
Status UpdateDriver::update(StringData matchedField,
                            BSONObj original,
                            mutablebson::Document* doc,
                            bool validateForStorage,
                            const FieldRefSet& immutablePaths,
                            BSONObj* logOpRec,
                            bool* docWasModified) {
    // TODO: assert that update() is called at most once in a !_multi case.

    _affectIndices = (isDocReplacement() && (_indexedFields != NULL));

    _logDoc.reset();
    LogBuilder logBuilder(_logDoc.root());

    if (_root) {

        // We parsed using the new UpdateNode implementation.
        UpdateNode::ApplyParams applyParams(doc->root(), immutablePaths);
        applyParams.matchedField = matchedField;
        applyParams.insert = _insert;
        applyParams.fromReplication = _modOptions.fromReplication;
        applyParams.validateForStorage = validateForStorage;
        applyParams.indexData = _indexedFields;
        if (_logOp && logOpRec) {
            applyParams.logBuilder = &logBuilder;
        }
        auto applyResult = _root->apply(applyParams);
        if (applyResult.indexesAffected) {
            _affectIndices = true;
            doc->disableInPlaceUpdates();
        }
        if (docWasModified) {
            *docWasModified = !applyResult.noop;
        }

    } else {

        // We parsed using the old ModifierInterface implementation.
        // Ask each of the mods to type check whether they can operate over the current document
        // and, if so, to change that document accordingly.
        FieldRefSet updatedPaths;

        for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
            ModifierInterface::ExecInfo execInfo;
            Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
            if (!status.isOK()) {
                return status;
            }

            if (execInfo.context == ModifierInterface::ExecInfo::INSERT_CONTEXT && !_insert) {
                continue;
            }


            // Gather which fields this mod is interested on and whether these fields were
            // "taken" by previous mods.  Note that not all mods are multi-field mods. When we
            // see an empty field, we may stop looking for others.
            for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
                if (execInfo.fieldRef[i] == 0) {
                    break;
                }

                // Record each field being updated but check for conflicts first
                const FieldRef* other;
                if (!updatedPaths.insert(execInfo.fieldRef[i], &other)) {
                    return Status(ErrorCodes::ConflictingUpdateOperators,
                                  str::stream() << "Cannot update '" << other->dottedField()
                                                << "' and '"
                                                << execInfo.fieldRef[i]->dottedField()
                                                << "' at the same time");
                }

                // We start with the expectation that a mod will be in-place. But if the mod
                // touched an indexed field and the mod will indeed be executed -- that is, it
                // is not a no-op and it is in a valid context -- then we switch back to a
                // non-in-place mode.
                //
                // TODO: make mightBeIndexed and fieldRef like each other.
                if (!_affectIndices && !execInfo.noOp && _indexedFields &&
                    _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
                    _affectIndices = true;
                    doc->disableInPlaceUpdates();
                }
            }

            if (!execInfo.noOp) {
                status = (*it)->apply();

                if (docWasModified)
                    *docWasModified = true;

                if (!status.isOK()) {
                    return status;
                }
            }

            // If we require a replication oplog entry for this update, go ahead and generate one.
            if (!execInfo.noOp && _logOp && logOpRec) {
                status = (*it)->log(&logBuilder);
                if (!status.isOK()) {
                    return status;
                }
            }
        }

        // Check for BSON depth and DBRef constraint violations.
        if (validateForStorage) {
            for (auto path = updatedPaths.begin(); path != updatedPaths.end(); ++path) {

                // Find the updated field in the updated document.
                auto newElem = doc->root();
                for (size_t i = 0; i < (*path)->numParts(); ++i) {
                    newElem = newElem[(*path)->getPart(i)];
                    if (!newElem.ok()) {
                        break;
                    }
                }

                // newElem might be missing if $unset/$renamed-away.
                if (newElem.ok()) {

                    // Check parents.
                    const std::uint32_t recursionLevel = 0;
                    auto parentsDepth =
                        storage_validation::storageValidParents(newElem, recursionLevel);

                    // Check element and its children.
                    const bool doRecursiveCheck = true;
                    storage_validation::storageValid(newElem, doRecursiveCheck, parentsDepth);
                }
            }
        }

        for (auto path = immutablePaths.begin(); path != immutablePaths.end(); ++path) {

            if (!updatedPaths.findConflicts(*path, nullptr)) {
                continue;
            }

            // Find the updated field in the updated document.
            auto newElem = doc->root();
            for (size_t i = 0; i < (*path)->numParts(); ++i) {
                newElem = newElem[(*path)->getPart(i)];
                if (!newElem.ok()) {
                    break;
                }
                uassert(ErrorCodes::NotSingleValueField,
                        str::stream()
                            << "After applying the update to the document, the (immutable) field '"
                            << (*path)->dottedField()
                            << "' was found to be an array or array descendant.",
                        newElem.getType() != BSONType::Array);
            }

            auto oldElem =
                dotted_path_support::extractElementAtPath(original, (*path)->dottedField());

            uassert(ErrorCodes::ImmutableField,
                    str::stream() << "After applying the update, the '" << (*path)->dottedField()
                                  << "' (required and immutable) field was "
                                     "found to have been removed --"
                                  << original,
                    newElem.ok() || !oldElem.ok());
            if (newElem.ok() && oldElem.ok()) {
                uassert(ErrorCodes::ImmutableField,
                        str::stream() << "After applying the update, the (immutable) field '"
                                      << (*path)->dottedField()
                                      << "' was found to have been altered to "
                                      << newElem.toString(),
                        newElem.compareWithBSONElement(oldElem, nullptr, false) == 0);
            }
        }
    }

    if (_logOp && logOpRec)
        *logOpRec = _logDoc.getObject();

    return Status::OK();
}
Example #6
0
    Status UpdateDriver::update(const StringData& matchedField,
                                mutablebson::Document* doc,
                                BSONObj* logOpRec) {
        // TODO: assert that update() is called at most once in a !_multi case.

        FieldRefSet targetFields;
        _affectIndices = false;

        _logDoc.reset();
        LogBuilder logBuilder(_logDoc.root());

        // Ask each of the mods to type check whether they can operate over the current document
        // and, if so, to change that document accordingly.
        for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
            ModifierInterface::ExecInfo execInfo;
            Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
            if (!status.isOK()) {
                return status;
            }

            // If a mod wants to be applied only if this is an upsert (or only if this is a
            // strict update), we should respect that. If a mod doesn't care, it would state
            // it is fine with ANY update context.
            const bool validContext = (execInfo.context == ModifierInterface::ExecInfo::ANY_CONTEXT ||
                                 execInfo.context == _context);

            // Nothing to do if not in a valid context.
            if (!validContext) {
                continue;
            }


            // Gather which fields this mod is interested on and whether these fields were
            // "taken" by previous mods.  Note that not all mods are multi-field mods. When we
            // see an empty field, we may stop looking for others.
            for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
                if (execInfo.fieldRef[i] == 0) {
                    break;
                }

                if (!targetFields.empty() || _mods.size() > 1) {
                    const FieldRef* other;
                    if (!targetFields.insert(execInfo.fieldRef[i], &other)) {
                        return Status(ErrorCodes::ConflictingUpdateOperators,
                                      mongoutils::str::stream()
                                      << "Cannot update '" << other->dottedField()
                                      << "' and '" << execInfo.fieldRef[i]->dottedField()
                                      << "' at the same time");
                    }
                }

                // We start with the expectation that a mod will be in-place. But if the mod
                // touched an indexed field and the mod will indeed be executed -- that is, it
                // is not a no-op and it is in a valid context -- then we switch back to a
                // non-in-place mode.
                //
                // TODO: make mightBeIndexed and fieldRef like each other.
                if (!_affectIndices &&
                    !execInfo.noOp &&
                    _indexedFields.mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
                    _affectIndices = true;
                    doc->disableInPlaceUpdates();
                }
            }

            if (!execInfo.noOp) {
                status = (*it)->apply();
                if (!status.isOK()) {
                    return status;
                }
            }

            // If we require a replication oplog entry for this update, go ahead and generate one.
            if (_logOp && logOpRec) {
                status = (*it)->log(&logBuilder);
                if (!status.isOK()) {
                    return status;
                }
            }

        }

        if (_logOp && logOpRec)
            *logOpRec = _logDoc.getObject();

        return Status::OK();
    }