Firestore security rules allow one document to be public - javascript

I'm working on an Angular app using Firestore here I have a bookings functionality. I want users to publicly read and write to bookings if there provided reference_no exists within one of the documents.
Here is how my document is structured:
These are my current security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /jobs/{job} {
allow read, write: if request.resource.data.property.reference_no == resource.data.property.reference_no;
}
match /{document=**} {
allow read, write: if isSignedInUser();
}
//functions
function isSignedInUser(){
return request.auth.uid != null;
}
}
}
This is how I'm querying this:
findByReferenceNo(reference_no): Observable < any > {
return this.afs
.collection(this.jobsCollection, ref => ref.where('property.reference_no', '==', reference_no))
.snapshotChanges()
.pipe(
map((actions) => {
return actions.map((snapshot) => {
const data = snapshot.payload.doc.data();
const id = snapshot.payload.doc.id;
return {
id,
...data
};
});
})
);
}
But not sure why I'm getting: Error: Missing or insufficient permissions.
Note: I'm not signed in while accessing this.

As far as I know, in order to guarantee query performance, Firestore security rules validate your query and not each individual document. This means that a query is only allowed if the rules can validate that the query won't retrieve more data than is allowed.
In your current model that is simply not possible, because it would require that the security rules check each individual document. For some examples of queries that can be secured, have a look at the Firestore documentation on securing queries.

Related

Firebase GET request blocked by simple firebase rules

I have the following collection group query:
const userInRooms = await firestore()
.collectionGroup('userRooms')
.where('uid', '==', authenticatedUser.uid)
.get();
And it works fine.
But since I added security rule:
match /rooms/{docId} {
allow read;
allow write;
match /userRooms/{docId} {
allow read;
allow write;
}
}
userRooms is subcollection in rooms.
It stopped working and I getting:
NativeFirebaseError: [firestore/permission-denied] The caller does not have permission to execute the specified operation.
Cascading the sub-collections rules that way doesn't work for collection group queries. A recursive wildcard must be present at the beginning of the path so it'll match any collections with that name. Try:
match /rooms/{docId} {
//...
}
match /{path=**}/userRooms/{docId} {
allow read, write: if true;
}
Do change the rules as required instead of allowing everyone to read the database (unless they are allowed to).

Firestore rules not allowing read given valid UID

I have written the following firestore rule to limit reads on the users collection by only allowing the request if the caller's UID matches the document ID. There is parity between the users collections' document ID and the UID in Firebase Authentication.
function isAuthenticated() {
return request.auth.uid != null;
}
// Users collection
match /users/{userId} {
allow read: if isAuthenticated()
&& request.auth.uid == userId;
}
I have a document in the collection with ID Eli90wRvWkfKcOfn1C4DBDqxQTz1. When hitting firestore with the same authentication user, I get the following error:
If I remove the check for the request.auth.uid == userId, it results in this rule and a successful read:
// Users collection
match /users/{userId} {
allow read: if isAuthenticated();
}
The query that is being called is:
export const getUser = (uid: string) => {
return db.collection('users').where('id', '==', uid).limit(1).get();
};
I've seen many people use the UID check in their rules but why isn't it working in my case?
Your security rules match this code:
db.collection('users').doc(uid).get();
So here we access a single specific documents, with the document ID being the UID of the user.
If you instead want to query for the UID in a field in the document(s), you can secure that with these rules:
match /users/{doc} {
allow read: if resource.data.id === request.auth.uid;
}
So now the rules match your original code, and only allow reading documents where the value of the uid field matches the uid of the current user.

Firestore Rule That Checks Single Document Not Authenticating?

I'm having what I think maybe a simple issue with Firebase Rules, but I can't seem to get this to work. I have a document 'Companies', with multiple subcollections inside. I want to set up a rule that checks for admins in an array (with each array item being a string of the firebase userId) inside a 'company' document and allows them to read/write all subcollections of that document.
Here's the data structure in a single company document:
company1 {
admins: ["userid1", "userid2", "userid3"],
}
Here's my firebase rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /companies/{company}/{document=**} {
allow read, write: if request.auth.uid in get(/databases/$(database)/documents/companies/$(company)).data.admins
}
}
}
Here's an example of a query that's not working when it should:
let ref = db.collection("companies");
//get current users company
ref
.where("admins", "array-contains", firebase.auth().currentUser.uid)
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
this.company = doc.data();
this.company.id = doc.id;
});
});
I hope my question makes sense :)
I found an answer, I hope it helps anyone who might come upon this.
I ended up adjusting the data structure to include the company document ID as a field in the user doc. Then, I created these rules to allow users to read/write their own user doc based on firebase authentication, as well as read/write their companies based on a field in the user doc:
service cloud.firestore {
match /databases/{database}/documents {
// Allow users to create and edit the document for themselves in the users collection
match /users/{user} {
allow read, write: if request.auth.uid == user;
}
// Allow users to create a company for themselves upon signup
match /companies/{company} {
allow create: if request.auth.uid != null
}
match /companies/{company}/{document=**} {
// Allow users to read/write data on thier own companies
allow read, write: if company in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.companies
}
}
}

Firestore returns insufficient permissions, even tough it shouldn't

I have the following rules set up for my Firestore database:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /collections/{document=**} {
allow read;
allow write: if isAdmin();
}
match /general/{document=**} {
allow read;
allow write: if isAdmin();
}
match /inquiries/{document=**} {
allow write;
allow read: if isAdmin();
}
match /orders/{document=**} {
allow write;
allow read: if isAdmin() || resource.data.userID == request.auth.uid;
}
match /products/{document=**} {
allow read;
allow write: if isAdmin();
}
match /users/{userId} {
allow write, read: if belongsTo(userId);
}
function belongsTo(userId) {
return request.auth.uid == userId
}
function isAdmin() {
return resource.data.admin == true;
}
}
}
As you can see, everybody is allowed to read /products and its documents plus subcollections. Which works for the products, but somehow the product's subcollection (every product has one called collection-colors) can't be read.
FirebaseError: Missing or insufficient permissions.
Code causing the error:
retrieveCollectionColors(name) {
this.db.collectionGroup('collection-colors', ref => ref.where('product', '==', name))
.valueChanges().subscribe( (val: []) => {
this.collectionColors.next(val);
}, error => {
console.log(error);
});
}
The rules you have right now don't apply at all to collection group queries. You'll need to write a special rule for that. From the documentation:
Secure and query documents based on collection groups
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}.
So, if you want to allow collection group queries for "collection-colors", it will look something like this:
match /{path=**}/collection-colors/{doc} {
allow read: ...
}
This will apply to all subcollections with the given name. You can't selectively allow or disallow subcollections based on the name of the parent collection.

firestore security rule resource.data is empty object

In firestore security rule, the resource.data is an emtpy object always, is this a bug or something ?
My firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /hospitals/{document=**}{
// allow read :if resource.data.size() == 0; //this return true, resource.data is an empty object
allow read :if resource.data.name != null; // this doesn't work
}
}
}
My javascript:
auth().onAuthStateChanged((user) => {
if (user) {
//db is the firestore instance
db.collection('/hospitals').get()
.then(printResult)
} else {
}
})
this is my current database snapshot
solved :
thanks for Frank's answer
the issue rely on that firestore security doesn't evaluate the actual document value when we query a over multiple document , in my case
//this doesn't firestore doesnt' evaluate the documetn
db.collection('hospitals').get()
//this will work ,if you need to compare the actual value
db.document('hospitals/somehospital').get()
Security rules don't filter data by themselves. They merely enforce rules on what data a client can read. Your client is currently trying to read all hospitals. Since your security rules have restrictions on what data a client can read, they reject this operation.
You need to ensure that what your client requests is no more than what the security rules allow, by reading the data through a query that matches the security rules. So something like
db.collection('/hospitals')
.where("name", ">=", "")
.get()
.then(printResult)
Note that this does require that the document has a name field, otherwise the name can't be empty.
For more info, see:
the Firestore documentation on securing queries
Firestore select where is not null

Categories