I have to check a document value != a string value in the Firebase Security rules.
So I have to first check if the document exists and then check the value.
allow read: !get(path).data || get(path).data.value !== 'xyz';
As I am calling the get() method twice, will it count 2 reads? If yes, how can I write the logic so that I fetch the document data once and reuse it?
As per the documentation, you are only charged once.
You are only charged one read per dependent document even if your rules refer to that document more than once.
Related
I have a collection named triggers I want to limit the number of documents per user on this collection, let's say 5.
I can create a counter with a Firebase function and set it as read-only, but I do not know how to limit it to the rules.
With Firestore Rules, is it possible to limit?
I can create a counter with a Firebase function and set it as read-only, but I do not know how to limit it to the rules.
You can read the document where you are storing the count as shown below (e.g. in users collection):
match /triggers/{triggerId} {
allow create: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.count < 5;
}
As Cloud Functions run asynchronously, this rule might fail if the user attempts to add multiple documents at once (i.e. document is added before the count is updated). You can alternatively add documents through a Callable Cloud Function as well and perform all validations there.
My firestore rules
match /fruits/{fruit} {
allow read: if request.auth.uid == resource.data.user;
}
Now on reading a single document, there is no problem
db.collection("fruits").doc('apple').get()
but when I try to get the collection, it gives me permission-denied
db.collection("fruits").get()
When you try to query all documents in 'fruits' collection, the security rules check if user field in those documents is equal to UID of user requesting data. If any one document doesn't match the condition, it'll throw a permission error. Instead you should add a where() clause in your query as shown below:
db.collection("fruits").where("user", ==, currentUserUID).get()
This query will find documents where user field matches user's UID and all matched docs will pass the security rule if request.auth.uid == resource.data.user;. Security rules are not filters and that means they won't filter out documents from collection based on the rule. You must write a query that way.
Use existing data in Firebase Security Rules:
When writing data, you may want to compare incoming data to existing
data. This lets you do things like ensure a field hasn't changed, that
a field has only incremented by one, or that the new value is at least
a week in the future. In this case, if your ruleset allows the pending
write, the request.resource variable contains the future state of the
document. For update operations that only modify a subset of the
document fields, the request.resource variable will contain the
pending document state after the operation. You can check the field
values in request.resource to prevent unwanted or inconsistent data
updates:
I don't understand the meaning of "pending" and "pending write" in the above explanation.
I can't imagine it.
Is it possible to suspend writes by security rules?
After the 'update operation' is done, the document is updated, so I don't know what the word "pending document state" means.
Pending write in this case means the actual write that you are currently making and that the firebase rules are checking if it is allowed or not. As mentioned in the documentation this is made to guarantee data consistency.
I think the best way to understand this is by looking at the example shared in the documentation:
match /cities/{city} {
allow update: if request.resource.data.population > 0
&& request.resource.data.name == resource.data.name;
}
In this case, the idea is to not allow an incoming request (which is the said pending write), to make the population field have a value lower than 1 or the name value being changed.
I have an 'owner' field in my documents to entitle only the owner to read the document, and only a new document that its 'owner' field is the uid of the user, can be written:
allow read: if request.auth.uid == resource.data.owner;
allow create, write : if request.auth.uid == request.resource.data.owner;
The creation and update rule works as expected! I tested and saw that if the 'owner' field of the record in the new data is not the user's UID then it doesn't work!
The problem is the 'read' section. I wasn't able to read records. Only when I changed to allow read: request.auth.uid != null, I was able to read.
I triple checked that the records has an 'owner' field that is exactly the same as the UID, also in debug.
I'm have experience with Firebase, and I have no idea what is the problem here.
Since you indicate that "[You weren't] able to read records" (with an S at records), your problem most probably comes from the fact that security rules are not filters, as explained in the documentation:
Once you secure your data and begin to write queries, keep in mind
that security rules are not filters. You cannot write a query for all
the documents in a collection and expect Cloud Firestore to return
only the documents that the current client has permission to access.
You don't show the query used to read the Firestore documents, but let's imagine your documents are in a collection named collection.
With a query like
query = db.collection("collection")
query.get().then(....);
you are querying independently of the User ID, hence the problem.
You need to adapt your query with the where() method, as follows:
//get the value of uid
query = db.collection("cities").where("owner", "==", uid)
query.get().then(....)
I'm attempting to setup security rules that allow access to a collection, based on the value of a document field in a subcollection.
This works as expected when retrieving an individual document by id, which is a get operation. However, when querying main_collection (a list operation), this fails with a "permission denied" error. Since there is only a single document in the collection, this is not a case where I don't have permission to some of the documents being queried, such as on this question.
My database structure looks like the following. It contains the collection being listed (main_collection), which has a single document (some_doc), which has a single subcollection (sub_collection), which has a single document (another_doc).
/main_collection/some_doc/sub_collection/another_doc
another_doc has one string field someFieldValue.
For this example, my query is of the entire collection, which is the single document. In my actual application it only queries the documents it expects to have access to, but the end result here is the same because I cannot filter against a document's subcollection from the client library.
firestore.collection('main_collection').get()
These are my security rules.
service cloud.firestore {
match /databases/{database}/documents {
match /main_collection/{mainColDoc} {
// This operation works
allow get: if subCollectionDocumentHasField('someFieldValue');
// This operation fails with permission denied
allow list: if subCollectionDocumentHasField('someFieldValue');
// This checks for the existence of a field on the subcollection's document
function subCollectionDocumentHasField(fieldName) {
return get(/databases/$(database)/documents/main_collection/$(mainColDoc)/sub_collection/another_doc).data.keys().hasAny([fieldName]);
//return get(/databases/$(database)/documents/main_collection/some_doc/sub_collection/another_doc).data.keys().hasAny([fieldName]);
}
}
}
}
The subCollectionDocumentHasField function checks for the existence of the someFieldValue field on the document another_doc. In this function, if I replace the $(mainColDoc) variable with the hard-coded document id some_doc, the list operation is successful. Since the $(database) path variable can be used in this context, I would expect that others could be as well.
Is this a bug or expected behavior?
This is actually the expected behavior, you can't use Firebase's rules to filter the results of your query.
A typical scenario would be to have collection of messages, where each message refers to its creator.
You can't simply add a rule where reading is only allowed on messages for which creator is the authenticated user, to filter automatically the messages of the current authenticated user.
The only way to go is to query with filter on the client side (or through a Cloud function).
The documentation is very clear about this :
When writing queries to retrieve documents, keep in mind that security rules are not filters—queries are all or nothing. To save you time and resources, Cloud Firestore evaluates a query against its potential result set instead of the actual field values for all of your documents. If a query could potentially return documents that the client does not have permission to read, the entire request fails.
From Firebase's documentation
I opened a ticket with Google and confirmed effectively what #José inferred from usage, which is that a security rule "is only checked once per query".
For clarification, while a security rule on a list operation will typically not query the contents of a document (to avoid potenitally-poor performance), there is at least one condition when it will query the contents of a document. This is when the security rule is guaranteed to return only one document. When this guarantee is met, the single document's contents will be queried because high performance can be maintained; the same as on a get operation.
So, in the linked example in my question where the list operation's rule is referencing a parent document, this guarantee is met and the parent document's contents will get queried.
Also, in my example where the list operation's rule is referencing a hard-coded document id, this guarantee is met and the hard-coded document's contents will get queried.
For the sake of stating it explicitly, for a list operation, in any case where Firestore cannot guarantee that its rule will only query a single document, access will be automatically denied, by design.
To reiterate what the other answers say, but stated in a slightly different way: The query must be consistent with the security rules, before any query documents are looked at, or it will fail with permission denied.
For example, if all of the documents in a sub-collection happen to match the security rule (e.g., your create and list rules both require the owner field is "X"), the query still must match the security rules (e.g., the query must also filter on owner is "X") or it will fail with a permission denied error, independent of the actual content of the sub-collection.