skip to Main Content

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. you need to use the exists, get and array functions. You can see the functions of working with lists here: https://firebase.google.com/docs/reference/rules/rules.List

    You can also write primitive functions such as (this is just an example of what you should do, it doesn’t work):

    rules_version = '2';
    service cloud.firestore {
        match /databases/{database}/documents {
    
            function isSignedIn() {
                return ((request.auth != null)
                && (request.auth.uid != null));
            }
    
            function getArtifactPath(id) {
                return (/databases/$(database)/documents/artifacts/$(id));
            }
    
            function getArtifactData(id) {
                return get(getUserPath(id)).data;
            }
    
            match /artifacts/{id}{
                allow read, write: if (
                    isSignedIn() && (
                        request.auth.uid in resource.data.usersIds ||
                        request.auth.uid in getArtifactData(resource.data.parentId).data.usersIds ||
                        request.auth.uid in getArtifactData(getArtifactData(resource.data.parentId).data.parentId).data.usersIds
                    )
                );
            }
        }
    }
    

    I also advise you to have a full usersIds in each document, because the rules do not support loops and you will have to paint N levels manually (see the example below). In addition, you will pay for reading each document that will be extracted at the rule level.
    If you need a tree structure, you can place your own rules in one of the documents and use it in the rules. For example, use the path /artifacts/a/b/${docId} and place rules like { "/artifacts/a/b": ["userId1", "userId2"]} in the document, and then use request.path, request.auth.uid, string and array functions.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search