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
Related
Security Rules
I am trying to grant permission based at a document field:
match /users/{user}/{documents=**} {
allow read, write: if resource.data.uid == request.auth.uid
}
Firebase query
Here is how my query looks:
query(collection(db, "users", match.params.uid, "promotors"));
Error message
But I keep geting this message:
FirebaseError: Missing or insufficient permissions.
Your query is not in any way checking the data in a field in the documents, so it will never meet this part of your rules: resource.data.uid.
Instead what you seem to have is a case where the document ID matches the UID of the user, which you can check with:
match /users/{user}/{documents=**} {
allow read, write: if user == request.auth.uid
}
Also see the documentation on content owner only access.
I am developing a Firebase project where I am using firestore.
I am querying Firestore to get Chat Messages from the messages collection. I want only messages beloning to a certain conversation:
const messages = await db
.collection('messages')
.where('room', '==', room)
.onSnapshot(snap => {//stuff})
So fa so good. It works. Things go wrong when I set up security rules.
If I do somthing simple, such as:
allow read: if request.auth != null;
everything is fine. But if I want to allow access only to users whose uid is included in the 'partiesIDs' message object property, things go wrong:
allow read: if
request.auth.uid == resource.data.pertiesIDs[0] ||
request.auth.uid == resource.data.parties[1];
The strangest thing of all is that I have in place a very similar rule for update, which works as expected:
allow update: if
(request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['read', 'notified'])) &&
(request.auth.token.name == resource.data.parties[0] ||
request.auth.token.name == resource.data.parties[1]);
The query for the second rule (which works) looks like this:
const update = await db
.collection('messages')
.doc(docid)
.update({read: true, notified: true})
I m stuck! Can anybody shed some light into this mistery?
The problem is that Firestore security rules are not filters. I strongly suggest reading that documentation.
When you write rules to place conditions on the reading of documents within a collection, the client is obliged to make a query that matches exactly the conditions of the rules. When you make a requirement that some data must exist in some field, then your query must match that by filtering for only documents that would satisfy the contents of the fields required by the rules. The rules will not extract only the matching documents to return them. You can think of the client query as demanding the full set of documents that match the given filters, and the rules as rejecting that demand because the conditions are not satisfied.
However, you have a bit of a problem here, because queries don't have a way of specifying array indexes. It's not possible to make a query that requires index 0 of an array field must contain a certain value.
I suggest rethinking your document data and rules, and structure them in such a way that the client app can exactly match their requirements.
Solved!
#Doug Stevenson thanks for having pointed me to the right direction.
The rules I have in place are the following:
allow read: if request.auth.token.name == resource.data.receiver || request.auth.token.name == resource.data.sender;
allow create: if request.auth != null;
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['read'])) && (request.auth.token.name == resource.data.parties[0] || request.auth.token.name == resource.data.parties[1]);}
allow delete: if false;
With these rules I am able to do the following:
First Load: load chats messages of a given room through a cloud function (it has admin priviledges, so no security rules issues there)
Get Incoming Messages: add an onsnapshot realtime listener to get all messages where receiver is the logged in user and property room is equal to the open room
await db.collection('messages').where("receiver","==",user.displayName).where("room", "==", room).orderBy('timestamp').onSnapshot(snapshot => { //Do Stuff })
Updated Read Status: when a message is displayed, the receiver updates the read property of the mssage in firestore:
const update = await db.collection("messages").doc(id).update({read: true});
Render Read Status in the UI in real time: add a second onsnapshot realtime listener to get all messages where sender is the logged in user and room is equal to the open room
const readUpdate = await db.collection('messages')
.where("sender", "==", user.displayName)
.where("room", "==", room)
.orderBy('timestamp')
.onSnapshot(snapshot => { //Do Stuff })
Send Message: user sends new messages through firestore. On the sender side, new messages are rendered locally through JS (Not through a real time listener).
On the receiving side, new messages are delivered and displayed throught the listener described in 2)
const deliverMSG = await db.collection('messages').doc(newID).set(newMsgObj);
//Render Message
renderMSG({id: newID, data: newMsgObj});
Perhaps this is not the most elegant way to handle this, but it works pretty well and is definitelly secure.
If you have any extra tip, I ll be very happy to read it.
For context, I display my Firebase app's configuration in the app.js file (is this safe?) and in order to give a user their data I list the following code:
db.collection('whispers').where("sender", "array-contains",
userID).orderBy('time').onSnapshot(snapshot => {
let changes = snapshot.docChanges();
changes.forEach(change => {
renderSay(change.doc);
});
});
Could a user remove the 'where' condition and access all data in this collection? I only use anonymous sign in so nothing special required in accessing their own data besides using the same device and not clearing data.
I doubt that could be possible by reverse-engineering but the actual security lies in the security rules.
You can change them from the Firebase Console
Here is a video from Firebase and here is the documentation
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'users' collection
match /users/{userID} {
allow read: if request.auth.uid === userID;
allow write: if request.auth.uid === userID;
}
}
}
Using the above security rules will allow users to read and write their data ONLY.
So even if they reverse-engineer your code, it will harm their data only and other data will be safe.
For more details please check the links above and you will get familiar with wildcard [{userID} in this case]
PS: Only you or anyone with access to your Firebase Console can change the rules
EDIT: This Medium Article has many types of rules explained. Please have a read there too :D
I'm trying to make a simple direct message part. I have a document with some meta informations and a subcollection with the single messages.
When I try to get the messages the rules are valid in the simulator, but in js I get everytime: Uncaught (in promise) FirebaseError: Missing or insufficient permissions.
The mongodb structure:
chatRooms
roomId
metaInformation (senderId/Name, recieverId/Name, timestamp, etc.)
messages
messageId
name
text
timestamp
The rules:
match /chatRooms/{chatRoom} {
allow create: if request.auth.uid != null;
allow update, delete: if resource.data.uid == request.auth.uid;
function isChatPartner() {
return parentDoc().receiverId == request.auth.uid || parentDoc().senderId == request.auth.uid;
}
function parentDoc() {
return get(/databases/$(database)/documents/chatRooms/$(chatroom)).data;
}
match /messages/{message} {
allow read: if isChatPartner();
}
}
The js-request:
db.collection("chatRoom").doc(_roomId).collection("messages").get().then(msg => {
console.log(msg);
})
Has anyone an idea what maybe could be wrong?
Your database and rules don't match. In the rule, the top level collection is called "chatRooms", but in your code, it's called "chatRoom". The collection names need to match exactly.
There's another problem. Your code is trying to get all of the documents in the subcollection, but the rules don't allow that. The rules are checking certain fields for access. This won't work in security rules, since rules are not filters (be sure to read and understand those docs). The query must only request documents that will definitely pass the rules - the rules will not check each document and exclude the ones that don't match.
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.