I posted a question about this yesterday but I'm creating a new one with more details.
Firestore .setData is blocked by update rule not create
I've run the simulator and the rules work there. Also when I create the document and change setData in the swift code to update the code works. It appears to only fail when creating the document. But the catch is that when I remove the update rule or simply change it to allow update: if false; the setData (or seen as create by the rules) executes properly. I have no clue whats going on nor do I know of any tools for getting a better insight.
match /users_real/{userID} {
allow create: if true;
allow read: if isOwner(userID);
allow update: if (request.writeFields.size() == 1);
}
set data:
self.docRef.collection("users_real").document("adfadsf").setData(post) { (error) in
if let error = error {
print("He dead!: \(error.localizedDescription)")
}
else {
print("it worked, for now")
}
}
Firebase Support confirms that there is a bug related to the evaluation of request.writeFields.size(). No estimate was given of when it will be fixed.
The existence of the bug can be demonstrated with the following rules:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// This should always evaluate to true, but does not.
allow create: if (request.writeFields.size() == 1) || (request.writeFields.size() != 1);
allow update: if true;
}
}
}
Although the create rule should always evaluate to true, an attempt to create a city fails with Permission Denied. It seems that the problem with request.writeFields affects not only the rule in which it appears, but also other rules for the path. For the rules shown above, an attempt to update an existing city also fails with Permission Denied.
Related
This is a follow up on this post: Firebase Firestore security rules - read all nested docs based on resource (but it's null)
I'm having a hard time figuring how Firestore works!
So let's say I have this Bases collections:
{
name: "Base 1",
roles : {
user_1: true
}
},
{
name: "Base 2",
roles : {
user_2: true
}
}
I want to fetch all Base where user_1 is in resource.roles. I do :
db.collection('bases').where('roles.user_1', '==', true).get()
And it works great. All server side so no one can try to alter the filter: All good.
But, I want to enforce this filter with a security rule in firestore. Just to be sure! So I've wrote:
function hasRole(){
return request.auth.uid in resource.data.roles
}
match /bases/{document=**}{
allow read: if true; // signedIn() && hasRole();
}
match /bases/{baseId}{
// allow read: if signedIn() && hasRole();
allow update, delete: if false && signedIn()
allow create: if signedIn()
}
And it works when I try to access a specific resource in the rule simulator but now my .get() method receive a not-enough permission. Funny enough, When I uncomment the read into /bases/{baseId} it works perfectly (if I comment the /bases/{document=**} of course).
So I can make it work but I'd still like to understand why the {document=**} rule work in simulator and not when I'm trying to fetch from a server.
Thanks a lot for your help :)
P.S: I didn't find a way to simulate a nodejs .get() in the simulator which is not super convenient to learn this stuff. Any resource on that will be read and helpful.
Once again, I'm trying to set up a database on firestore. It's a very simple one which would store emails list from my landing page. But I can't getting work.
The Javascript Console, throw this error:
InvalidStateError: A mutation operation was attempted on a database that did not allow mutations."
This is my security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{databases}/documents {
match /emailList/{list} {
allow create: if true;
allow update: if true;
allow delete: if false;
}
}
}
It might be something easy, but this project already carried out with my patience
Make sure your FireFox is setup correctly.
For IndexedDB to work you need history turned on.
Options > 'FireFox will Remember history'
I am having difficulty trying to diagnose a particular rule in my firestore.rules file. See that question here for context.
Is there a way to debug the firestore.rules file and/or functions? I'm using unit testing and the emulators to test my rules, but I would really love to see exactly what values are being evaluated by the rules engine.
For instance, here is my firestore.rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin();
}
}
}
}
}
function isAdmin() {
// removed for security
}
function belongsToCurrentClass() {
// retuns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
What I'd love to do is set breakpoints or step through the code. When attempting CRUD operations on a organizations/{orgId}/classes/{classId}/students/{studentId} path I'd love to inspect exactly what values the orgId, classId, and studentId variables are holding, as well as the resource and request parameters. I'd love to inspect exactly which document (if any) is returned by the get request in belongsToCurrentClass and what the return value is.
Does anyone know of any way to do this? I think I'd answer my question referred to above in 10 seconds if I could just see the data being evaluated.
There is a local emulator for Cloud Firestore security rules. This is your best (and really only) tool for digging into security rule execution. There is no step-through debugging, but you can see a lot of debug output in the console.
https://firebase.google.com/docs/rules/emulator-setup
We can add the built-in debug function to rules. As noted in a comment, you'll see an unhelpful message like this in the browser:
Received: [path] Expected: [bool]. for 'list' # L6
On the plus side, we won't forget to remove debug messages. Tail the log file to see the output: tail -f firestore-debug.log
For example, to see which paths are being called:
match /databases/{database}/documents {
match /{document=**} {
allow create, read, update, delete: if debug(request.path);
}
}
Let's assume we have a Firestore collection called todos, where each todo will look something like this:
{
name: "Buy milk",
completed: false,
user: "eYtGDHdfgSERewfqwEFfweE" // some user's uid
}
Now, I want to prevent modification of the user field during updates (in other words - make this field read-only).
Believe me, I've done my research. My update rule looks like this:
allow update: if request.auth.uid == resource.data.user
//&& !request.writeFields.hasAny(["user"]);
//&& !(request.writeFields.hasAny(["user"]));
//&& !request.resource.data.keys().hasAny(["user"]);
//&& !('user' in request.resource.data);
//&& ('user' in request.resource.data) == false;
//&& !('user' in request.writeFields);
None of the above (commented out) work. They all result in an error: Error: Missing or insufficient permissions..
But...
It gets even more interesting! Because if we compare some of the above rules for positive outcome (aka true) they will work!
For example, this one works perfectly (assuming we include user field in our request):
allow update: if request.resource.data.keys().hasAny(["user"]) == true;
BUT this one doesn't work (assuming we do NOT include the user field in the request):
allow update: if request.resource.data.keys().hasAny(["user"]) == false;
Can anyone explain me what is going on here? Is it possible this is actually a bug in Firestore?
At "Writing conditions for Cloud Firestore Security Rules" section "Data validation" example #2
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /cities/{city} {
allow update: if request.resource.data.population > 0
&& request.resource.data.name == resource.data.name;
}
}
}
So request.resource.data.user == resource.data.user should work for you? CMIIW
Ref: https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation
When you update a document the security rules compare the document that results from the update with the conditions of the security rules. This means that if a field exists in the document on the server but not in the data you push in the update, the security rules will see that field from the old data in the update. For example:
In the document store on the server:
{
reviewerID: sam123,
title: "It sucks",
description: "Because it does",
rating: 1
}
and then your update is:
{
description: "but actually it's not so bad",
rating: 3
}
Then what the security rules see in request.resource.data is:
{
reviewerID: sam123,
title: "It sucks",
description: "but actually it's not so bad",
rating: 3
}
Which means that even though the update didn't push a change to reviewerID or title those fields do exist in the data you're evaluating.
To make sure that the data is unchanged you need to compare the new data with the old data:
request.resource.data.reviewerID == resource.data.reviewerID
the line:
!request.resource.data.keys().hasAny(["user"]);
Would be correct in preventing the data from existing in the document. It is not something that would allow it to exist but make it immutable.
See:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /cities/{city} {
allow update: if request.resource.data.population > 0
&& request.resource.data.name == resource.data.name;
}
}
}
From:
https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation
and:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
From:
https://firebase.google.com/docs/firestore/security/rules-fields#forbidding_specific_fields_in_new_documents
In firebase firestore security rules, I want to allow a request only if a particular document does not exist. My code is:
match /users/{user_id} {
allow create: if !exists(/databases/$(database)/merchants/$(request.auth.uid));
}
I am pretty sure the document does not exist but it does not work.
Both exists() and !exists() give false somehow, or maybe !exists() raises some error.
I have even tried:
match /users/{user_id} {
allow create: if exists(/databases/$(database)/merchants/$(request.auth.uid)) == false;
}
Is there any way to make this work?
Are your rules inside the database block? Cause I had a similar problem and was actually putting the rule bellow the database block, and my rule was using the $(database)parameter just like yours
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user_id} {
allow create: if !exists(/databases/$(database)/merchants/$(request.auth.uid));
}
}
}