Mongo.update throws forbidden error even when using _id

Hello,

I am currently facing a small problem with a client-side Collection Update. I can bare the usual 403: Not permitted. Untrusted code may only update documents by ID. and I do understand why it is so, and I am fine with it.

The problem is that it restricts updates on array elements! Take this mongodb documentation: https://docs.mongodb.com/v3.2/reference/operator/update/positional/

A proper query could look like so:

Datasources.update({
        _id:obj.id,
        "elements.filename":obj.filename
    },{
        $set:{"elements.$.finished":true}
});

Unfortunately Meteor seems to be too strict on the _id rulecheck and does not allow such query to pass. I have worked around it using method calls but I’d rather directly use the update for latency compensation and easier code.

I do not want to hack my way through, so is there a possibility to loosen the _id check to allow updates that are even more precise than using just an id?

I’m not sure I see the advantage to allowing this… You are already updating by the _id which is a unique index. You couldn’t possibly have another document with that _id so the second key is necessary.

Yes remove the second query, _id is sufficient

The second key is necessary to target a specific element in an array of the target document.

Please consider the document is something similar to this:

{
    name:"my_csv_repo",
    total_size:123456,
    elements:[
        {filename:"sheet1.csv",finished:true},
        {filename:"sheet2.csv",finished:true},
        {filename:"sheet3.csv",finished:false}
    ]
}

The usual mongo query (at least that I am aware of, not so much an expert yet) to update the finished field of the sheet3.csv element is to use this query:

Datasources.update({
        _id:obj.id,
        "elements.filename":"sheet3.csv"
    },{
        $set:{"elements.$.finished":true}
});

The only other way I know of doing this is to find() the whole document to fetch the elements array, loop through it to find the correct filename, apply the modification and save the whole element array again. This seems quite heavy compared to using a mongoDB query alone.

Actually, the usual query would probably be more like the following:

Datasources.update({
        _id:obj.id
    },{
        $set:{"elements.$.finished":true}
});

Which would work, because mongo IDs are unique, which means you target only one document. All you have to do is obtain the _id of the document that matches {"elements.filename":"sheet3.csv"} first, and then execute your update.

This throws “The positional operator did not find the match needed from the query” this is to be expected as we cannot use the $ operator as we didn’t include any lookup in the query.

I’ll paste the interesting parts of the documentation to help figure out what I’m trying to do:

The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array.

When used with update operations, e.g. db.collection.update() and db.collection.findAndModify(), the positional $ operator acts as a placeholder for the first element that matches the query document, and the array field must appear as part of the query document.

Ah, right.

How about this:

Since you already know what the document looks like, and you have obtained the _id, can you specify the position in the array to update? Something along these lines:

let index = 2
Datasources.update({
  _id:obj.id
},{
  $set:{"elements." + index + ".finished":true}
});

Yes that would be a way to go around the problem. That would require me to implement a way to loop through all the elements array to find the one with the correct filename.

Another way to go around it, as I posted above is to simply run the query server-side, which I do and prefer as it is a lot less error-prone. But I have to work out on latency compensation and all that.

Although it is not a blocking problem, I think that this should get addressed or at least assessed as this is only one example of a couple other update queries that could potentially get blocked.

Imagine if I was looking for a $geoinstersect inside my array instead of just a string. That would become very tedious to implement…

Just a random thought: wouldn’t exposing the method on both client and server automatically implement optimistic UI (latency compensation)?

Actually, I hadn’t even tried as I was expecting to receive the same error anyways… But it does work. I have rarely used client-side methods as most of the mongo queries I could just run directly.

That’ll do the trick. Not sure though if it closes the subject of allowing more fields in the query, but it sure fixes my problem.

Oh, I see now… I hadn’t had enough coffee yet…