firebasegoogle-cloud-firestorefirebase-authenticationfirebase-security

how can i write firestore rules to accomodate a multi user tree data structure? -- think sharable todo items with subitems


The firestore data represents a collection of artifacts. Each artifact has a usersIds field (more about this later) and a parentId field that is null (if this is a root artifact) or hold an id to another artifact in the same collection.

Root artifacts can be shared between multiple users. So, every document with a parentId equal to null has an array of id values in its usersIds field. This artifact and every other artifact subordinated to it in the collection can be viewed and edited by the users on that array.

Permissions are only configurable at the root level. Child artifacts cannot override what was set in their root.

For example:

{ id: 'a', parentId: null, usersIds: ['joe', 'jane'] }
{ id: 'a1', parentId: 'a' } 
{ id: 'a11', parentId: 'a1' } 
{ id: 'b', parentId: null, usersIds: ['mary'] } 
{ id: 'b1', parentId: 'b' } 

Based on the example above, joe can view a, a1, and a11, but not b or b1.

How can I write firestore rules that impose what was discussed before? Can I use a recursive function?


Solution

  • This is gonna be hard to do in security rules, as there is no way to loop over the necessary documents. It helps for this to keep in mind that security rules don't filter the actual data, but instead merely ensure that a query only requests data that it is authorized to read.

    And just like I don't think that you can capture your requirements in security rules on the current data structure, I can't even think of a query that would only request the correct documents. Firestore queries can only filter on data in the documents that they return, and in this case you're actually trying to filter on data in "parent" artifacts - that may not be returns.

    For this type of authorization model, you'll typically need to denormalize the user IDs for everyone who has access to an artifact into that specific artifact. So you'd end up with this:

    { id: 'a', parentId: null, usersIds: ['joe', 'jane'] }
    { id: 'a1', parentId: 'a', usersIds: ['joe', 'jane'] } 
    { id: 'a11', parentId: 'a1', usersIds: ['joe', 'jane'] } 
    { id: 'b', parentId: null, usersIds: ['mary'] } 
    { id: 'b1', parentId: 'b', usersIds: ['mary'] } 
    

    With this data structure, you query suddenly becomes quite simple, and your security rules can check the usersIds field of the document that is actually being considered - rather than needing to do one of more levels of parent lookups.