Unable to base security rule condition on resource data in Firebase - firebase

I am attempting very simple thing and that is matching request.auth.uid to a field value in my transaction documents (like this resource.data.useruid) in Firebase security rule in order to get transactions of a particular logged in user. However, I don't get any documents while querying for them and get an error instead.
This is how the collection looks like - just one document there with useruid field.
The field's value is mapped to the users uid (screenshot taken in the Authentication -> Users tab.
And the rule looks like this
I should get the one document back but every time I query the documents with that user logged in (I am using angularfire2 for those purposes) I get Error: Missing or insufficient permissions.
If I modify the rule condition to return always true or if I only check for truthiness of request.auth.uid I get the query result alright. The funny thing though is that with resource.data involved - eg. checking for value of the amount field in the firebase rule - the condition is never met. I tried to write it like
allow read, write: if resource.data.amount == 3
and got the error again. Seems like I don't get the resource.data Map at all.
I feel like I am missing something obvious, although after reading the guides, it seems alright to me and I am already out of ideas. The debugging capabilities (or lack of) make the whole process very slow.
Could you please explain to me, why I don't get the resource.data Map in the firebase security rule or point me to a place where the problem might be?

You have most probably missed one specific point in the doc: your query fails "because it does not include the same constraints as your security rules". See https://firebase.google.com/docs/firestore/security/rules-query#secure_and_query_documents_based_on_authuid
The following, with your security rules works perfectly:
firebase.auth().signInWithEmailAndPassword("xxxx#xxxx.com", "xxxxx")
.then(function (info) {
db.collection("transactions").where("userid", "==", info.uid).get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
});
});
If you remove the where clause, you get the exact error you are getting

Related

Why is this Cloud Firestore security rule suddenly failing?

For some time I was using the following rule for Cloud Firestore in my Flutter project:
"You can see chats if you're logged in, a member and the chat was not flagged".
match /chats/{chatId} {
allow read: if signedIn() && request.auth.uid in resource.data.members && resource.data.flagged == false;
}
It used to work and to me, it seems correct. But recently, it started to fail. When I use only
allow read: if signedIn();
It works just fine. Any idea what might be the problem? In the Firebase emulator, I can see it also fails, but there is no explanation. Obviously, members and flagged fields exist (when a chat document is available).
Could this have happened after updating a particular package, like cloud_firestore?
Any ideas?
I found out, finally, what was wrong. According to this page, "the result set should satisfy the rule's condition". However, I thought my query was exactly the same as my rule and I didn't know what was wrong.
It turns out, this is taken quite literally.
In my query I had:
where('flagged', isNotEqualTo: true) and my rule was flagged == false. I needed to change that to flagged != true. This is very confusing. It seems like a bug to me, and otherwise, I would be very curious to know the reason behind this.

Firestore rules: Allow list operation only if filtered by ID

Me and my team are wondering how to correctly write security rule in firestore,
which allows list operation only if filtered by id like so:
First one should succeed
db.collection('questions')
.where(firebase.firestore.FieldPath.documentId(), 'in', allowedIds)
.get()
Second one should fail
db.collection('questions')
.get()
Currently we are doing this with the following rule:
match /questions/{question_id} {
allow list: if question_id != null
}
And for now it works
However we get the following WARNING in the firebase console:
WARNING The sub-expressions are not comparable, so this comparison will always return true.
Which is weird, because for now it works (in the emulators) - which means in this case it returns false
So here are a few questions:
Is this the correct way to achieve our goal?
How long will this work (will it stop working in future versions)?
If this is wrong - what is the correct way to do it?
Is this discrepancy between emulators and production environment?
Thank you in advance!

Firestore security rule get vs. list

Suppose I'm trying to make a webpage readable only when the user has the exact link to it, could I achieve this using a combination of the document ID and security rules as follows?
eg. I have documents stored at
/posts/{postID}
I have security rules set up:
match /posts/{postID} {
  allow get, write: if true;
  allow list: if false;
}
Would simply disabling the "list" operation make it such that you can only access a document if you already know its exact ID? It looks like the case to me but I'm not sure if I'm missing any edge cases.
Would simply disabling the "list" operation make it such that you can
only access a document if you already know its exact ID?
Yes, this is right.
Any query to the posts collection will return a "Missing or insufficient permissions" error, even a query that queries with the exact ID, like:
firebase
.firestore()
.collection('posts')
.where('__name__', '==', 'exactID')
.get()

Firestore security rule that only allows empty documents

I'm basically trying to use a firestore collection as a an email list. Anyone can create a document that has their email as the id and nothing more. The tricky part is the "and nothing more" bit. When no data is provided in the request, request.resource is undefined which you can't check for in security rules to my knowledge. Is this possible? Or is it necessary to have something like one mandatory field for this use case?
Having empty documents regularly leads to issues down the line. Why not require a single marker field, and validate that in rules?
request.resource.data.keys.hasOnly("marker")
For the benefit of others looking to make an email list in firestore, this is the full rule I ended up using:
match /email-list/{email} {
allow get: if true;
allow list: if false;
allow create: if request.resource.data.keys().hasOnly(["marker"])
&& request.resource.data.marker == true
}

Debugging security rules for Cloud Firestore

I am setting up security rules for my Google Cloud Firestore database. I am trying to allow deletion of a document only if its timestamp value is more than 30 days in the past using the following logic:
allow delete: if
resource.data.locked == false
&& (request.time - resource.data.timeStamp).seconds > 2592000;
When I try this I get Error: Missing or insufficient permissions. So, first question - am I going about this wrong or is my logic flawed?
And as a followup question, is there a way to debug rules? Perhaps a console.log equivalent whereby I can see the result of conditional rules as they are applied and check that I'm not submitting a string in place of a timestamp or anything daft like that?
I'm assuming that request.time is 'now' and that my resource.data.timeStamp is correct and that one minus the other returns a Duration and that thatDuration.seconds is returning a number but I'm a newb to this and any one of those assumptions could be wrong and it would be great to be able to see these values as they are processed.
Cheers all
I am not checked, please try something like this
request.time < resource.data.timeStamp + duration.value(30, "d");
And your second question, i don't know. There is no simulator like in Realtime database. Remember its still in beta.
There's now a local emulator to help debug Firestore rules. It won't allow you to step through the rules like a "real" debugger, but it'll at least give you more flexibility to test and verify which rules are broken.

Resources