4

My firestore database is structured so that "leagues" is the top collection, and each league contains a field named after each approved userID (with a number value).

Each league also has a subcollection "users" of documents named after each approved userID.

 

Here is an example firestore query:

FirebaseAuth mAuth = FirebaseAuth.getInstance();

// userId = ABCDEF123 for this example
String userId = mAuth.getCurrentUser().getUid();

FirebaseFirestore firestore = FirebaseFirestore.getInstance();
firestore.collection("leagues").whereLessThan(userId, 99).get();

 

Can someone please tell me why this rule works:

match /leagues/{league} {
    allow read, write: if resource.data.ABCDEF123 != null;

but not this:

match /leagues/{league} {
    allow read, write: if resource.data.request.auth.uid != null;

 

Also, why does this rule work:

//"ZYXWV987" is an example of a league the user is in
match /leagues/{league} {
    allow read, write: if exists(/databases/$(database)/documents/leagues/$('ZYXWV987')/users/$(request.auth.uid));

but not this:

match /leagues/{league} {
    allow read, write: if exists(/databases/$(database)/documents/leagues/$(league)/users/$(request.auth.uid));

 

The error I get is "com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions"

 

I am looking to understand how these rules work, and consequently how to implement proper rules for my database!

EDIT:

I now realize that this works (still a WIP):

match /leagues/{league} { 
    allow read, create, update: if request.auth.uid != null; 

    //only ever deleting a single league at a time 
    allow delete: if exists(/databases/$(database)/documents/leagues/$(league)/users/$(request.auth.uid));

    match /{docs = **} { 
        allow read, write: if exists(/databases/$(database)/documents/leagues/$(league)/users/$(request.auth.uid))} 
}

and I sort of understand what's going on (I can't use the {league} wildcard when reading/writing potentially more than one league in a request?), but I'm still not exactly sure why?

Eddie
  • 132
  • 1
  • 11

1 Answers1

3

Can someone please tell me why this rule works:

match /leagues/{league} {
    allow read, write: if resource.data.ABCDEF123 != null;

but not this:

match /leagues/{league} {
    allow read, write: if resource.data.request.auth.uid != null;

I guess there is no field named request in resource.data. How should the dot syntax know that you want to evaluate the last part (request.auth.uid) first and use the result as the next key? I'm not sure but you could try resource.data[request.auth.uid] instead.

Also, why does this rule work:

// "ZYXWV987" is an example of a league the user is in 
match /leagues/{league} {
    allow read, write: if exists(/databases/$(database)/documents/leagues/$('ZYXWV987')/users/$(request.auth.uid));

but not this:

match /leagues/{league} {
    allow read, write: if exists(/databases/$(database)/documents/leagues/$(league)/users/$(request.auth.uid));

From the docs on security rules:

Every database request from a Cloud Firestore mobile/web client library is evaluated against your security rules before reading or writing any data.

The first rule is evaluated and depending on the result, the user is allowed to read ALL or NONE league documents. The second rule would have to be evaluated for every single document depending on the content. That is not possible before reading them all.

You have to define your security rules in a way that Firestore can evaluate them based only on the definition of your query independent of the possible result.

RobDil
  • 4,036
  • 43
  • 52
  • resource.data[request.auth.uid] works! And the rest makes sense too. Thanks! – Eddie Mar 23 '18 at 08:24
  • Followup question: how do I check in FS rules for when I'm deleting a field (i.e. "documentReference.update(fieldID, FieldValue.delete();") if that field's value is FieldValue.delete() ? "request.auth.uid in request.writeFields" is giving me "true", but "request.resource.data[request.auth.uid] != null" and "request.resource.data[request.auth.uid] = null" are both giving me "false"/"error" (I'm not sure how to check which) – Eddie Mar 23 '18 at 08:37
  • I didn't really understand what you are trying to do but just to make sure this is not a typo - have you tried ***==*** null? – RobDil Mar 23 '18 at 08:47
  • Ha, it's a typo on here, but not in my actual rules! Here is the full rule (currently, for troubleshooting) that's not working: `allow update: if request.auth.uid in request.writeFields && (request.resource.data[request.auth.uid] == null || request.resource.data[request.auth.uid] != null);` – Eddie Mar 23 '18 at 09:05
  • From my app, users can change their access levels in a group `docref.update(userID, level)` or delete themselves entirely `docref.update(userID, FieldValue.delete())`, and users are all fields in the document (with their access level as their value)... Updating the levels works with the above rule, but deleting doesn't... Furthermore, deleting the user does work with just `allow update: if request.auth.uid in request.writeFields`. – Eddie Mar 23 '18 at 09:07
  • Ultimately, I want my rule to be something like `allow update: if request.auth.uid in request.writeFields && request.resource.data[request.auth.uid] == FieldValue.delete();` ....... or `allow update: if request.auth.uid in request.writeFields && request.resource.data[request.auth.uid] < resource.data[request.auth.uid];` – Eddie Mar 23 '18 at 09:07
  • Here is a question that might help you: https://stackoverflow.com/questions/47452855/delete-a-field-from-firestore-with-a-dynamic-key – RobDil Mar 23 '18 at 09:45
  • And ```(request.resource.data[request.auth.uid] == null || request.resource.data[request.auth.uid] != null)```is always true and could be removed altogether. – RobDil Mar 23 '18 at 09:47
  • That part of the rule was only for troubleshooting to show there's an error (I'm guessing the error is the field does not exist) when the field's value is FieldValue.delete()... I'm also confident that I'm deleting the field correctly; it's how to account for that in the security rules that is the problem. – Eddie Mar 23 '18 at 10:22