Permission based update dilemma

Hi everyone!

This topic follows my precedent one where I was asking about conditional updates.

Let’s say I have the following object:

{
    "_id" : ObjectId("5ed0d70c009dab1100a6209c"),
    "name" : "toto",
    "value": 42,
    "permissions" : {
        "write_name" : [ 
            "permission1",
            "permission2",
        ],
        "write_value" : [ 
            "permission3",
            "permission4",
        ],
    },
}

As you would expect, write_name and write_value describe the write permissions of name and value.

When a user tries to update this objects, write permissions must be checked. But the update can’t be applied partially in function of each field permissions, since that could produce incoherent objects in our DB (not with this particular example object, but with much more complex ones).
So if a user wants to update any field of this object, he must have the 4 permissions described above.

To implement this I have 2 solutions:

  1. Use find_one() to query the object itself (without its permissions) and make sure it exists. Then use find_one_and_update() to query the object with all the permission fields (therefore not finding it if the user does not have all the required permissions) and update it with a classic update document.

  2. Use only find_one_and_update() with aggregations pipelines and check if the update went through afterwards:

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

But these two solutions both have their inconvenient

  • The first one performs 2 database actions, therefore meaning that it is not atomic and that it might lead to undefined behaviours, even if it will probably be very rare.

  • The second one checks every other permission of the object to conditionally update one field, this means the cost of it will be squared (the average object having 10 fields). Also note that the example above, for example purposes, is lighter than the actual aggregation pipelines we perform.

So my question is: Do you think the cost of option 2 will always be much higher that the cost of option 1 (even though options 1 uses two db operations?), and how bad is it having an update method that is not atomic?