Why is this Cloud Firestore security rule suddenly failing? - firebase

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.

Related

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
}

How do you debug Firestore security rules?

I'm crying myself to sleep on this one.
My getAfter is returning an object that only has 1 field, as every other field type is incorrect. Which I have no idea how to check without any debugging tools (I can't see the data, so its all guess and check).
Here is a watered down version of my rules for users.
match /users/{userId} {
function isValidUser(user) {
return user.id is string &&
(user.address is string || user.address == null) &&
(user.dateOfBirth is number || user.dateOfBirth == null) &&
user.email is string &&
user.name is string &&
(user.phoneNumber is string || user.phoneNumber == null);
}
function isValidWrite(userId, user) {
return signedIn() &&
writeHasMatchingId(userId, user) &&
isValidUser(user);
}
allow read: if signedIn();
allow create: if signedInAndWriteHasMatchingId(userId) &&
userHasId(userId) &&
isValidUser(request.resource.data); // Tested
allow update: if isValidWrite(
userId,
getAfter(/databases/$(database)/documents/users/$(userId))
);
}
and this is the transaction I am trying to run.
const user1Ref = this.userCollection.doc(user1Id);
const user2Ref = this.userCollection.doc(user2Id);
const batchWrite = this.store.batch();
batchWrite.update(user1Ref, {
"details.friend": user2Id,
});
batchWrite.update(user2Ref, {
"details.wishlist": true,
});
batchWrite.commit();
If I comment out the isValidUser(user) line, the operation succeeds. If I leave any line uncommented out inside the function isValidUser(user) except user.id is string, it fails.
Why would the getAfter document only have the id field and no others when they are listed in the Firebase console? Is there a way to output or debug the value of getAfter so I can see what it even is?
I'm answering based on just one line of your question:
Is there a way to output or debug the value of getAfter so I can see what it even is?
There kind of is - at least in 2020.
When one runs something in the Rules Playground (Rules Simulator, see bottom left), the steps taken in the rule evaluation are shown like this:
This list sometimes gives indications that help figure out what the rules evaluator is doing. It's a bit tedious that one needs to 'click' the steps open, individually, instead of seeing true/false just by glancing. But it's better than nothing.
Note: I presume this feature is under development by Firebase. It sometimes seems to give wrong information - or I have failed to read it correctly. But it may help, and looks like a good place for providing such information to the developers. We really would like to see: with the current data, the built query document, and the rules, how does Firebase see it and why does the rule evaluate to true or false?
Another approach, not mentioned here yet and likely not available at the time the question was raised, is wrapping your rules with debug().
Why this is cool?
Allows to see the values suspected of not being right; I still use the same comment-out-narrow-down method that #ColdLogic nicely described in one of their comments
Why this is not enough?
There is no tagging about which value was output; just eg. int_value: 0. Debug would benefit from eg. printing the first 10 letters of the equation it's evaluating, in the output.
Security Rules rejection reasons are still awfully short, as false for 'update' # L44.
the line number always points to the main expression being evaluated. Never to a function called, or a subexpression with && that really causes the fail.
Firebase could fix this (not change the output syntax; just give a more detailed line number). That would eliminate the need to comment-out-and-narrow-down.
The output goes to firestore-debug.log (fairly hidden), so one needs to open yet another terminal and keep an eye on it.
Debugging Security Rules is unnecessarily difficult - and I'm afraid it means people don't use their full potential. We should change this.

Unable to base security rule condition on resource data in 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

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.

Data fails to load due to permissions but only initially

What's happening is that usually the first time I authenticate a user after enough time has passed that the session has expired my requests for data that the user should have access to are denied. The requests will start working eventually with no actual rules or code changing. It can take from 2 to 10 minutes but will eventually right itself.
I don't have hard data on this; it's just something I've observed.
I have no idea what I can do about this. Is anyone else seeing this? Is this a known bug? I've search but haven't found any other accounts of this happening.
Thanks.
here are the rules:
{
"rules": {
"users": {
"$user": {
".read" :"$user == auth.id || data.child('email').val() == auth.email",
".write":"$user == auth.id"
}
},
"todos":{
"$list":{
".read":"data.child('members').hasChild(auth.id)",
".write":"newData.child('members').hasChild(auth.id)"
}
}
}
}
By default, your auth tokens live for 24 hours, so if you're seeing security errors before then, it's probably not related to your session. To verify this, you can attach a callback to tell you when your session has expired. See:
https://www.firebase.com/docs/javascript/firebase/auth.html
I suspect what's going on here is simply that the data your rules are depending on is changing and causing writes to succeed some times, and fail others. This is the intended behavior of Security Rules in Firebase. For example, if someone was added / removed from /todos/blah/members/ it would affect your ability to read at /todos/blah
One odd thing about your rules is that you appear to be allowing anyone to write to /todos/$list as long as the data they are writing contains a member list that contains the writer's user id. I suspect this is not your intended behavior. Perhaps you meant to make the write rules depend on the data that was already in Firebase rather than new data?

Resources