Reputation: 13
Hello to everyone reading this.
I am coding a flutter app for an hospital, that has this db structure.
I am having an issue fetching sessions data, exactly the following document.
Using the following method to get the lastSession a therapist made, using his therapistUID as the filtering field.
Future<Session> getLastSession() async {
Query query;
query = Firestore.instance
.collection("sessions")
.where("therapistUID",
isEqualTo: this.uid)
.orderBy("date", descending: true)
.limit(1); //this.uid = auth uid of current therapist.
try {
QuerySnapshot querySnapshot = await query.getDocuments(); //exception thrown here
if (querySnapshot.documents.isEmpty) {
throw Exception("Empty query");
} else {
lastSession = Session.fromDocument(querySnapshot.documents[0]);
return lastSession;
}
} catch (e) {
throw Exception("cannot get data from database");
}}
with the following rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /patients/{document=**} {
allow read,write,list: if checkPatientAccess(resource.data);
}
match /therapists/{document=**} {
allow read,write,list: if checkOwnership();
}
match /sessions/{document=**} {
allow read, write,list: if checkPatientAccess(get(/databases/$(database)/documents/patients/$(resource.data.patientUID)).data);
}
match /devices/{document=**} {
allow read, write,list: if false;
}
match /clinics/{document=**} {
allow read, write,list: if false;
}}}
function checkOwnership(){
return resource.id == request.auth.uid;
}
function checkPatientAccess(patient){
return request.auth.uid in patient.therapistUIDs;
}
Code is throwing this exception
Does anyone know why is it rejecting the query? keep in mind query is only one document, and as well there is only one document in the database that could fit those filters. Using testlab with same parameters works.
Upvotes: 0
Views: 82
Reputation: 598648
Firebase security rules do not on their own filter data, as that would not scale. This becomes clear when we look at:
match /sessions/{document=**} {
allow read, write,list: if checkPatientAccess(get(/databases/$(database)/documents/patients/$(resource.data.patientUID)).data);
}
function checkPatientAccess(patient){
return request.auth.uid in patient.therapistUIDs;
}
In order to secure your read operation, these rules would have to load each document and check the therapistUIDs
value in there. This would be an O(n) operation, while Firestore is guaranteed to return results on O(1). For this reason, such security rules don't work.
Your rules do work for reading a single document, but not for the list
operation.
If you can come with with a query that returns the data that you want, you may be able to secure that query. But since Firestore doesn't support any type of join in queries, you'd need to replicate the data you want to filter on from the patient
document into each session
document in order to make this work.
As discussed in the comments: Since your query ensures all documents have the same patientUID
, the get()
call in your rules is guaranteed to always get the same document, and thus the rules engine can guarantee that it will never return an authorized document for the query.
Pretty nifty actually.
Upvotes: 1
Reputation: 317322
It doesn't matter how many documents you request - Firestore security rules will not act as a filter on those documents. Please read and understand this documentation. It won't let you conditionally check something for each document to determine if it can be read. Your rules are trying to express that something must exist in a matching patient document for each session read, but that's not allowed. It simply will not scale the way that Firestore requires, and would be extremely costly for queries with large result sets.
Upvotes: 1