6
votes

I am trying to set up a ruleset for a new Firestore project where we have the following root collections:

users

organisations

Under users we have users/userid/ar/organisationid (ar for access rights)

This document should define what access rights the user have for a given organisation. The point being that many users can have access to one or more organisation in this saas application.

I want all the documents and all the subcollections under the organisation to be checked agains this access right.

This works perfectly for get and write, but listing of data gives an access error, and the only way I found to restrict the list data is to add an array on the document that list the usersids, and also filter on this client side. This is not a solution that is maintainable for production since there will be alot of subcollections with many documents.

Is there any suggestions? Any other way I can make the listing work for Firestore? Is there better way to structure the data, or anything I am missing?

Only other way I can see is to use firestore functions to do the check server side for the listing, but then we loose the awesome stuff in @angular/fire.

Filtering client side is not good enough security.

Thank you.

Rules as of now:

service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{userId} {
      allow read, write: if request.auth.uid == userId;
      match /ar/{organisationDoc} {
        allow read: if request.auth.uid == userId;
        allow write: if false;
      }
    }     

    // read
    match /organisations/{oId} {
      allow get: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId));
      allow list: if request.auth.uid in resource.data.users;

      match /{all=**} {
        allow get: if exists(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId));
        allow list: if request.auth.uid in resource.data.users;
      }
    }

    // write
    match /organisations/{oId} {
      allow create: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.create == true;
      allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.update == true;
      allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.delete == true;

      match /{all=**} {
        allow create: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.create == true;
        allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.update == true;
        allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)/ar/$(oId)).data.delete == true;
      }
    }

  }
}
2

2 Answers

0
votes

The problem you are encountering is caused by the fact that "security rules are not filters", see this documentation item.

In other words, your query must filter the organizations for which the user has (read) access rights, for example like you do with "array on the document that list the usersids". You could also have the lists of organizations as an array in the user doc.

Using a Cloud Function to "check server side for the listing" and (if I understand correctly) build your query based on the result of this Cloud Function call is really not ideal.

But what you can do with a Cloud Function is to populate/modify automatically the array listing the userids (or the organizations) when you create/modify/delete an organisationid document under users/userid/ar/organisationid.


Note that the doc says:

This behavior applies to queries that retrieve one or more documents from a collection and not to individual document retrievals

This is why your rules work for the get() method, as you mention.

1
votes

See documentation

Control Access with Custom Claims and Security Rules

The Firebase Admin SDK supports defining custom attributes on user accounts. This provides the ability to implement various access control strategies, including role-based access control, in Firebase apps. These custom attributes can give users different levels of access (roles), which are enforced in an application's security rules.