Create a firebase style permission hierarchy in mongoDB

问题: New to MongoDB, and working with NodeJS's mongodb package, I am trying to retrieve and update items based on a user's permission levels which are also stored in a collectio...

问题:

New to MongoDB, and working with NodeJS's mongodb package, I am trying to retrieve and update items based on a user's permission levels which are also stored in a collection.

I understand how to fetch all items:

 const collection = db.collection('events');
  // Find some documents
  collection.find({}).toArray(function(err, docs) {
    assert.equal(err, null);
    console.log("Found the following records");
    console.log(docs)
    callback(docs);
  });

But I wonder whether, given the following data structure:

{
    events: {
        1: {
            id: 1,
            name: "first event",
        },
        2: {
            id: 2,
            name: "2nd event",
        },
        3: {
            id: 3,
            name: "3rd event",
        },
    },
    permissions: {
        events: {
            1: {
                user1: "read",
                user2: "write",
            },
            2: {
                user2: "write",
            },
            3: {
                user1: "readwrite",
                user2: "write",
            },
        },
    },
 }

and given you are logged in as User1, the above fetch code can fetch only the relevant events which user1 has read access to according to the permissions specified?

For CRUDS such as updates, deletes and inserts, I can run a separate query and see whether the user has the needed access. However, get queries are common and IMHO should be straightforward so I wonder how this filtering can be done elegantly?


回答1:

First of all, I would simplify your data model, since there is a thing that well might bite you in your back later down the road. You should not use values as keys, as you did in the event documents. Clearly, those are arrays, and the array elements already have an id they can be referenced by.

Furthermore, your data model (and subsequently my derived one) is only good for a couple of thousand events, as there is a 16MB limit on documents. In order to mitigate this, I will show you an updated data model and how to deal with it, too.

One document for events

Data model

Here is my updated data model. We basically got rid of the unnecessary values posing as keys.

{
  "events": [{
      "id": 1,
      "name": "first event",
    },
    {
      "id": 2,
      "name": "2nd event",
    },
    {
      "id": 3,
      "name": "3rd event",
    }
  ],
  "permissions": [{
      "event": 1,
      "perms": [{
          "user": "user1",
          "permission": "read",
        },
        {
          "user": "user2",
          "permission": "write"
        }
      ]
    },
    {
      "event": 2,
      "perms": [{
        "user": "user2",
        "permission": "write"
      }]
    },
    {
      "event": 3,
      "perms": [{
          "user": "user1",
          "permission": "readwrite"
        },
        {
          "user": "user2",
          "permission": "write"
        },
      ]
    }
  ],
}

Which still gives you the possibility to search for specific events via db.events.find({"events.id":1},{"events.$":1}). This, of course works for each and every other property of an event.

Permission check

The basic question of wether a user has a permission to do a certain operation on a specific document can be rephrased as

Get me all documents with the properties I am interested in AND for which user Y has the permission to do Z.

This translates to a rather simple query:

db.events.find({
  // The event we want...
  "events.id": 1,
  // ...shall be returned provided the following conditions are met
  "permissions": {
    $elemMatch: {
      // The permissions for event with id 1...
      "event": 1,
      "perms": {
        $elemMatch: {
          // ...allow the user in question... 
          "user": "user1",
          // ... to read said event.
          "permission": "read"
        }
      }
    }
  }
}, {
  // Subsequently, we return all matching events in all documents...
  "events.$": 1
})

What we use here is that query conditions are evaluated as logical AND. If the user does not have the permission we requested ("read" in this example), no document is returned.

One document per event

Data model

However, as said, there is a 16MB size limit on MongoDB documents. So if you have a lot of events, or even when you are not really sure about the number of events you might have, we should approach the problem from a different (and even simpler) angle: One document per event. The data model gets stupidly simple:

// db.events.insertMany([
{
  "_id": 1,
  "name": "first event",
  "permissions":[
    {
      "user":"user1",
      "permission": "read"
    },
    {
      "user":"user2",
      "permission":"write"
    }
  ]
},
{
  "_id":2,
  "name": "2nd event",
  "permissions":[
    {
      "user": "user2",
      "permission": "write"
    }
  ]
},
{
  "_id":3,
  "name": "3rd event",
  "permissions":[
    {
        "user": "user1",
        "permission": "readwrite"
      },
      {
        "user": "user2",
        "permission": "write"
      },
  ]
}
// ])

Permission check

Now, let us say we want to check wether user1 can actually read (which includes readwrite) event 3 and if so, return it. Becomes even less complicated than before:

db.events.find({
  // The event we want...
  "_id": 3,
  // ...in case...
  "permissions": {
    $elemMatch: {
      // ... user1...
      "user": "user1",
      // ..has either the permission...
      $or: [{
        // ...to read or...
        "permission": "read"
      }, {
        // ... to readwrite.
        "permission": "readwrite"
      }]
    }
  }
}, {
  // Either way, do not return the permissions.
  permissions: 0
})

Conclusion

Regardless of which data model of the two you will choose, you can use the above queries as the query part of your update statements:

db.events.update(<queryFromAbove>,{[...]})

Needless to say, I would strongly suggest to use the "one-document-per-event" approach.

However, there are two things you need to keep in mind.

If something goes wrong, you need to do extra queries to find out what went wrong. The event might either not exist altogether, or the user might not have the necessary permission to execute the query/update. However, since this should be the exception, I personally could live with that.

The other thing to keep in mind is that you need to make sure that a user can never, ever change the permissions array. For rather obvious reasons.

  • 发表于 2019-01-07 16:30
  • 阅读 ( 188 )
  • 分类:网络文章

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除