"Conditional" object update

Hi everyone!

Before describing my issue I’d like to give you a bit of context. I am working on a GraphQL server coded in Rust that uses MongoDB Rust crate as a Database driver. One of the main goals of this project is to have a very safe permissions system.

My problem is related to write permissions.
Let’s say I have the following object:

{
    "_id" : ObjectId("5ed0d70c009dab1100a6209c"),
    "name" : "toto",
    "permissions" : {
        "read" : [ 
            "read1"
        ],
        "write" : [ 
            "write1"
        ],
    },
}

When a user tries to update this type of object, before the server actually updates it, it needs to make sure that:

  1. It exists => return a “not found” error if it doesn’t
  2. The user has the required permissions to update it, in this case “write1” => return “insufficient permissions” if it doesn’t

Now the problem with this is that it requires 2 Database interactions, a find_one for step 1 (to match the object itself) and a find_one_and_update for step 2 (to match its permissions to the user permissions and update if they are correct).
But this means that if user A is trying to update an object, and user B is trying to delete the same object, user B delete request might go off between user A step 1 and step 2 of its update. And that would lead to an “insufficient permissions” error when in reality the object does not exist.

So what I wanted to do was generating a guid before making the update, and then adding it to the updated object to check the result and ensure that the update went through (this would remove step 1 from the process and allow use to avoid mutex in our code since it is a big performance decrease). But this requires to be able to make a conditional update based on the field value and from what I’ve read there is no way to do that ($cond is not available as an update operator: https://docs.mongodb.com/manual/reference/operator/update-field/).

So my question is would there be a way to have some sort of field-based conditional object update using MongoDB? I also wonder if there would be a way to find a solution to my problem using aggregation pipelines but I haven’t found much success in that either.

Hello, @Thomas_Plisson! Welcome to the community!

Due to the concurrency docs, write operations will be put in a queue and execute one by one on Mongo server. That means, it delete operation was issued after update operation, it will execute after update operation.

Since Mongo v4.2, update operations can use aggregation pipelines for updates. That means, that you can use $cond inside it:

db.test1.updateOne({ _id: ObjectId("5ed0d70c009dab1100a6209c")}, [
  {
    $set: {
      name: {
        $cond: {
          if: {
            // check if user has required permission
            $in: [userPerm, '$permissions.write']
          },
          then: 1, // overwrite prop value
          else: '$name', // return origin value
        }
      }
    }
  }
]);

In my example, I used updateOne() method, because it returns convenient for you situation result:

{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 0 }

This output means that 1 document matched by provided query, but it was not updated - either it already had the required value before update (it was modified long time ago or just now by another user) or because user has not enough permissions (due to the code logic).

You can achieve then same with .findOneAndUpdate() method and { returnNewDocumet: true } option by comparing your value with the one returned with method.

You can not differentiate those two cases (already same value, not enough permissions) using this approach, especially if there is a high chance that same value can be updated by multiple users.

The thing is, that you’re trying to assign too much work on 1 operation, such over-optimizations can lead to code complications. In your case, it would be totally OK to have 2 requests do database:

  1. fetch the document you want to update (1st request)
  2. check permissions (throw Error, if user permissions are not enough to update it)
  3. update the document, if fetched version had not actual state (2nd request)

You may end up with only 1 request anyway, if user has not enough permissions or the document already had desired value :wink:

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.