Super basic firestore security rule won't work - firebase

I can't get the literal simplest firestore security rule I can write to work in the play ground. Just for testing, I've made a Cloud Firestore database with a collection named users. It has one field stuff. In the playground, these are my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user} {
allow read, write: if true;
}
match /{document=**} {
allow read, write: if false;
}
}
}
I'm simulating a get on location: /databases/(default)/documents/users, but it always fails due to the document=** match, and never matches /users/{user}. Why is this! Feels like I'm following the most basic examples from the docs.
Added a couple screenshots for clarify.

In the "Rules playground", in the location field, you don't need to enter /databases/(default)/documents/. This part of the path is already taken into account, as it is shown above the editable field with the pale grey (or greyed out) /databases/(default)/documents string.
So, by just entering users/C8YDk... it will work, since your rule allows reading the doc, due to an overlapping matching statement.
More info on how to use the playground is to be found here.

Related

How To DENY Access to entire Firestore database in security rules

Lets say I want to have a document in my Firestore having a key on whether to allow access to database or not.
Something like server_online = True
now in my firebase rules I want to check this rule first before going into rules for each collection and document.
I know that a complicated way of doing this is to put this thing in a function and then check this function along with other functions in my firebase rules for every access specifier, but that would be very long since i have a very long ruleset.
So how should i tackle this problem?
I wanted it to be something like.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
allow read: if get(/databases/$(database)/documents/server/status).data.status;
match /users/{userID} {
allow read: if request.auth.uid == userID;
allow create
}
Now first the above rule will check if the variable is True and if so only then proceed to the later ones.
I know that the above code wont work, as Firebase checks the bottom most rule first and doesnt overwrite access.
Any ideas on how to tackle this problem?
Since overlapping rules are OR'ed together, there's no easy way to enforce your AND condition on all rules in one place.
The shortest way I've found is to create a function (say isOnline) and then call that in all rules or in other *higher level) functions that my actual rules depend on.

Firebase rules not applying to subcollection documents

Here are my rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{id}{
allow read : if (request.auth.uid == resource.data.uid);
allow write : if false;
}
match /Class/{id}{
allow read : if (request.auth.uid == resource.data.instructor.uid || (request.auth.uid == resource.data.admin.uid));
allow write : if false;
}
match /Class/{id}/Topics/{doc} {
allow read:
if request.auth.uid == resource.data.topicOwnerUID || request.auth.uid == resource.data.adminUID
allow write: if false;
}
}
}
Everything is fine except trying to pull all the topics from Class/docs/Topics/docs. I use getDocuments and try to get all documents using limit(50), I only have 1 topic now, but I also tried making limit to 1 and still don't work.. In each of the Topics docs, there is an adminUID and a topicOwnerUID field. When the owner tries to pull all documents I am getting an error: "Error: Missing or insufficient permissions.". I checked and all the required fields are there and this should allow.
Edit: seems like if i remove .limit and just get a single doc with a specific docID it works. But this isn't what I want :/. Maybe I will have to make the subcollection into its own collection.
Edit2: doesn't work even if it is not a subcollection. completely lost rn. Looks like the only way is to get each doc individually but this ruins lazyLoading. Firebase is lagging behind. will be switching to another db.
Security rules do not mean anything without the matching queries - remember that Security Rules ARE NOT FILTERS - they will NOT "just give you the records that are allowed" - you MUST use queries to match your rules. If your query could return a document that isn't allowed by the rules, then the ENTIRE query is disallowed.
I can tell you from EXTENSIVE use that Firebase/Firestore do not lag behind in any way, and I use complex queries continuously.

nested firebase-firestore rules: owner id within parent document

I can't get these rules to work:
I've got a collection with projects, which all have an owner. The owner should be allowed to read/write his projects and the subcollection working_copies as well.
This implementation succesfully grants reading the project, but fails (Missing or insufficient permissions) when reading a working_copy from the sub collection. I suspect it tries to find an owner within the sub-document.
service cloud.firestore {
match /databases/{database}/documents {
match /projects/{projectId} {
allow read, write: if
resource.data.owner == request.auth.uid;
match /working_copies/{doc} {
allow read, write: if true;
}
}
}
I've also tried using this condition either in the project path or in the working_copies path, but it both fails as well:
get(/databases/$(database)/documents/projects/$(projectId)).data.owner == request.auth.uid
Everything above also fails when i use a recursive wildcard for nesting:
match /projects/{projectId=**} {
...
The strange thing is, i think the first version used to work until some days ago.
I use angular/angularfire and call the requests like this:
this.db.collection('projects').doc('3279').collection<ProjectData>('working_copies').valueChanges().pipe(...
In the rules simulator it's green lighted though.
I've finally found a workaround:
!('owner' in resource.data) || resource.data.owner == request.auth.uid
This makes it accept that the child document doesn't provide the owner once more. So it seems, when nesting rules, the parent rules are also applied to child documents.

Allowing access based on mapped key

It's a simple and common use case of security rule, but cannot make it work.
I have a document orgs/fooOrg on my Firestore(not RTDB), and it contains an object
{
"members": {
"fooUser": true
}
}
and the rule applied is
service cloud.firestore {
match /databases/{database}/documents {
match /orgs/{orgId} {
allow read: if "fooUser" in resource.data.members;
}
}
}
I expect all the document in orgs collection should be able to be read; however, the server says Error: Missing or insufficient permissions as a result of running
firebase.firestore().doc('orgs/fooOrg').get()
on a browser (using v4.5.0 and v4.5.1). Even
allow read: if resource.data.members["fooUser"] == true;
fails, too. What went wrong?
In my understanding, this should work according to this document
https://firebase.google.com/docs/firestore/security/secure-data#evaluating_documents_currently_in_the_database
I believe that it was working like a week ago. All the sudden, my working code started to generate the error, so I wrote this MCVE and tested on several different projects.
In addition, I found similar issues below, but a bit different from them, so not sure if it's the same reason (a bug on Firestore)
Firestore security rules based on map values
(My case, even getting a simple document fails)
Firestore read rules with self condition
(This case uses a value of a map. My case, a key is used)
Now seems that the issue is solved without changing code. No announcement, but seems that something is fixed by Firestore side.

Firestore security rules based on map values

I want to store if a user is permitted to read a document in the document itself, based on the user's email address. Multiple users should have access to the same document.
According to the documentation Firestore does not allow querying array members. That'S why I'm storing the users email addresses in a String-Bool Map with the email address as a key.
For the following example I'm not using emails as map keys, because it already doesn't work with basic strings.
The database structure looks like that:
lists
list_1
id: String
name: String
owner: E-Mail
type: String
shared:
test: true
All security rules are listed here:
service cloud.firestore {
match /databases/{database}/documents {
match /lists/{listId=**} {
allow read: if resource.data.shared.test == true
}
}
}
Edit: It also doesn't work if I use match /lists/{listId} instead of match /lists/{listId=**}
How I understand it, this security rules should allow reading access to everyone if the value in the map shared[test] is true.
For completness sake: This is the query I'm using (Kotlin on Android):
collection.whereEqualTo("shared.test", true).get()
.addOnCompleteListener(activity, { task ->
if (task.isSuccessful) {
Log.i("FIRESTORE", "Query was successful")
} else {
Log.e("FIRESTORE", "Failed to query existing from Firestore. Error ${task.exception}")
}
})
I'm guessing that I cannot access map values from the security rules. So what would be an alternative solution to my problem?
In the Firestore rules reference it's written that maps can be accessed like that resource.data.property == 'property' so, what am I doing wrong?
Edit: This issue should be fixed now. If you're still seeing it (and are sure it's a bug with the rules evaluator), let me know in the comments.
I've chatted with some folks here about the problem you're encountering, and it appears to be an issue with the security rules itself. Essentially, the problem seems to be specific to evaluating nested fields in queries, like what you're doing.
So, basically, what you're doing should work fine, and you'll need to wait for an update from the Firestore team to make this query work. I'll try to remember to update this answer when that happens. Sorry 'bout that!
Whenever you have (optional) nested properties you should make sure the property exists before continuing to check its' value eg.
allow read: if role in request.auth.token && request.auth.token[role] == true
in your case:
allow read: if test in resource.data.shared && resource.data.shared.test == true
, I was struggling a long time with roles until I realized that on non-admin users the admin field is undefined and firestore rules just crashes and doesn't continue checking other possible matches.
For a user without token.admin, this will always crash no matter if you have other matches that are true eg:
function userHasRole(role) {
return isSignedIn() && request.auth.token[role] == true
}

Resources