I am using Firebase Cloud Firestore, and I want to modify my rules to restrict users from querying a collection.
This should not be allowed:
firestore().collection("users").get()
But this should be allowed:
firestore().collection("users").doc("someUserId").get()
Currently, my rules look like this:
match /users/{userId} {
allow read;
}
but this rule allows the "users" collection to be queried.
How can I allow single document gets, but not collection queries?
You can break read rules into get and list. Rules for get apply to requests for single documents, and rules for list apply to queries and requests for collections (docs).
match /users/{userId} {
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
}
Just allow get and you'll be good:
match /users/{userId} {
allow get;
}
Reference: https://firebase.google.com/docs/rules/rules-language#:~:text=Convenience%20methods-,read,-Any%20type%20of
Related
I am having an issue where I am trying to query a subquery by a field, and I have tried the emulator as well as firebase support (still on going, but not solutions yet).
When running the following query:
const queryByNumber = query(
collectionGroup(this.firestore, 'residents'),
where('cellNumber', '==', number)
);
return from(getDocs(queryByNumber));
This query has the error of:
Property cellNumber is undefined on object. for 'list' # L6
This is what the rules look like at the moment
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{path=**}/residents/{residentDoc} {
allow write: if resource.data.cellNumber == request.auth.token.phone_number;
allow read: if request.auth.token.phone_number == resource.data.cellNumber;
//allow read; //temp permission till support fix
}
match /Units/{unitNumber} {
allow read: if request.auth != null;
function residentDoc(residentId) {
return get(/databases/$(database)/documents/Units/$(unitNumber)/residents/$(residentId))
}
match /pets/{petId} {
allow write: if residentDoc(request.resource.data.residentId).data.cellNumber == request.auth.token.phone_number;
allow read: if request.auth != null;
}
}
}
}
And this is the firestore data structure at the moment:
I have tried to change my query to have the array-contains in the where clause, but that doesn't change much.
Note: the allow read without the if check allows the data to be retrieved, so the query does work, just the rules to secure it are not`
The problem is in this condition in your rules:
request.auth.token.phone_number in resource.data.cellNumber
The in operation in Firestore security rules is only defined for maps and lists/arrays, but your cellNumber field is a simple string value, which does not define an in operation as you can see here.
If you want to check whether the phone number in the token is the same as the value of the field, use == instead of in. If you want to test whether it is in the field, consider using the matches method on the string.
Also see:
Firebase firestore security rules allow if document ID contains string
Using contains() with firebase rules
The issue was another query trying to get all resident data after the initial one was fired, which caused it to fail the rule because the second one wasn't querying by cellNumber anymore. I have changed the rules to validate by UID since that will always be provided
Im getting my documents based on a list of ids.
db.collection("fruits").where(db.FieldPath.documentId(), "in", fruitIds).get()
How should I write my security rules to allow the above call and to deny the below call
db.collection("fruits").get()
It's not possible exactly as you require. What you can do is set your rules like this:
match /fruits/{id} {
allow get: true;
allow list: false;
}
This allows clients to get a document if they know the ID, but make it impossible to query documents in bulk.
You will have to then code your client app request each document individually with a DocumentReference get() (instead of a Query with a where clause). The performance hit for this is negligible (no, there is not any noticeable performance gain for using an "in" query the way that you show here - and you are limited to 10 documents per batch anyway).
As #Doug covered in their answer, this is not currently supported in the way you expect.
However, by looking at the reference, you can at least limit list (query) operations by placing conditions on the orderBy and limit used by any queries to make it more difficult rather than outright blocking it.
Consider this answer educational, just fetch the items one-by-one instead and disable list/query access in your security rules. It is included here for those who simply want to obfuscate rather than outright block such queries.
This would mean changing your query to:
db.collection("fruits")
.where(db.FieldPath.documentId(), "in", fruitIds)
.orderBy(db.FieldPath.documentId()) // probably implicitly added by the where() above, but put here for good measure
.limit(10) // this limit applies to `in` operations anyway, but for this to work needs to be added
.get()
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /fruits/{fruit} {
allow read: if <condition>
// Limit documents per request to 10 and only if they provide an orderBy clause
allow list: if <condition>
&& request.query.limit <= 10
&& request.query.orderBy = "__name asc" // __name is FieldPath.documentId()
allow write: if <condition>;
}
}
}
Using those restrictions, this should no longer work:
db.collection("fruits").get()
But you could still scrape everything in smaller chunks using:
const fruits = [];
const baseQuery = db.collection("fruits")
.orderBy(db.FieldPath.documentId())
.limit(10);
while (true) {
const snapshot = await (fruits.length > 0
? baseQuery.startAt(fruits[fruits.length-1]).get()
: baseQuery.get())
Array.prototype.push.apply(fruits, snapshot.docs);
if (snapshot.empty) {
break;
}
}
// here, fruits now contains all snapshots in the collection
I have a firestore rule for a collection "orders" and i use the simulator tool to est my rules and they definitely pass for all reads, yet when i attempt to display the orders in my console i get an error
Rules
match /orders/{document=**} {
allow read;
allow write: if isAdmin();
allow create: if isOrderOwner();
}
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.userinfo.isAdmin;
}
function isOrderOwner() {
return get(/databases/$(database)/documents/orders/$(request.auth.uid)) == request.auth.uid
}
On my app, all i'm doing is console.log(orders) and i get this error:
Uncaught Error in snapshot listener: FirebaseError: Missing or insufficient permissions.
In my front end (react)
db.collectionGroup('orders').onSnapshot(snap => {
let ordersArr = []
snap.forEach(doc => {
if(doc.data().orderid)
ordersArr.push(doc.data())
})
setAllOrders(ordersArr)
console.log(ordersArr) // -> this console causes the insufficient permissions error
})
I can't figure it out, i very simply have allow read; for all documents in my orders collection.
Note: the orders are in a subcollection of the orders collection (i don' think that is the problem though)
You are using collectionGroup so if I assume orders is a sub-collection for each document in parent collection, let's say users or customers, the rules should ideally be something like this:
rules_version = '2';
service cloud.firestore {
match /{path=**}/orders/{orderId} {
allow read;
allow write: if false; // <- any rules you have for writes
}
}
I ran the following query and the read operation was allowed:
const snapshot = await firebase.firestore().collectionGroup("orders").get();
console.log(snapshot.size, "docs")
The {path=**} is a recursive wildcard as mentioned in the documentation.
The documentation says,
In your security rules, you must explicitly allow collection group
queries by writing a rule for the collection group:
Make sure rules_version = '2'; is the first line of your ruleset. Collection group queries require the new recursive wildcard {name=**}
behavior of security rules version 2.
Write a rule for you collection group using match /{path=**}/[COLLECTION_ID]/{doc}.
Because both isAdmin() and isOrderOwner() use a get() call, you are likely exceeding the limit for your query. You will need to apply a limit() to your collection group query and paginate through the results. As each rule uses two get() calls, you will only be able to retrieve up to 10 documents per query request.
Paraphrased from the Cloud Firestore Security Rules Documentation:
A limit of 10 exists(), get(), and getAfter() calls applies to single-document requests and query requests.
A limit of 20 exists(), get(), and getAfter() calls per request applies to multi-document reads, transactions, and batched writes.
For example, imagine you create a batched write request with 3 write operations and that your security rules use 2 document access calls to validate each write. In this case, each write uses 2 of its 10 access calls and the batched write request uses 6 of its 20 access calls.
Exceeding either limit results in a permission denied error.
I recently started a firebase project, and I need some help with the security rules. When A user signs up, I create a new document in a collection called "Users". The document name is encrypted by a server, and can only be decrypted by that same server. The problem I am having now is that if a malicious entity wanted to, they could get all of the documents in the collection by changing client-side code, and that would defeat the whole purpose of encrypting the data. So my question is: Is there a way to enforce that somebody can only read the data of their document in the collection, and block attempts to read the whole collection? (I am using Firestore by the way.)
Thanks so much!
FireBase Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
match /users/{userId} {
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow read, write;
}
}
}
}
You're duplicating rules:
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow read, write;
Since allow read is a combination of allow list and allow get, that last line make the two lines above it useless.
The minimum change is to remove read from the allows:
//signed in users can get individual documents
allow get: if request.auth.uid != null;
//no one can query the collection
allow list: if false;
allow write;
I suspect you'll want to tighten the allow write to only allow users to write their own document, but that's a separate problem.
I am using Firebase Firestore to store a list of transactions in a collection called transactions .
I use react to get the transaction from the collection, using a URL with the id of the transaction document: http://myurl.com/h1kj54h2jk35h
const id = match.params.id;
firebase.firestore().collection('transactions').doc(id).get()
The same way I create a new doc. I have not firebase authentication.
How can I secure my Firestore rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /transactions/{id} {
allow read, write;
}
}
}
If I don't allow write, I can't create new transactions.
If I don't allow read, I can't read the transaction, but I don't want to allow the read of all transactions. Only the once when the id from the URL is valid.
Thus, I am looking for a way to protect my database agains unwanted document creations and unwanted document reads.
I think you're looking for what are called granular operations. From that link:
In some situations, it's useful to break down read and write into more granular operations. For example, your app may want to enforce different conditions on document creation than on document deletion. Or you may want to allow single document reads but deny large queries.
A read rule can be broken into get and list, while a write rule can be broken into create, update, and delete.
So if you want to only allow creation of new documents, and getting of document for which a user must know the ID, that'd be:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /transactions/{id} {
allow create, get;
}
}
}