Flutter firestore snapshot always null or empty list - firebase

i am trying to fetch firestore snapshot but it always return null , i don't know where is the problem ..
please see the image here
my rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
my code 1:
await Firestore.instance.document("myDB/EG/EG/Cairo Governorate").collection("Alexandria Governorate").limit(10).getDocuments().then((snapshot)async{
print(snapshot.documents);//[]
});
my code 2:
await Firestore.instance.document("myDB/EG/EG/Cairo Governorate/Alexandria Governorate").get().then((snapshot)async{
print(snapshot.data);//null
});
Update:
how this document does not exist ?

In your first bit of code, an empty array means that your query didn't find any documents in the named collection.
In your second bit of code, a null for snapshot.data means that the documented requested doesn't exist.
In both cases, since we can't see your data and verify that it exists, there's nothing else that can be said. This has nothing to do with security rules, as your queries would fail completely if they were rejected by rules. I suspect the names in your code simply don't match what's in your database.

In Firebase docs regarding Delete Douments there is warning that when you delete document it does not delete its subcollections. This is why those docs are marked with this description. However subcollections should be still available.
In your both samples of code you use "Alexandria Governorate" as document while guessing from the screenshot its a collection.
I do not have playground to test it however it should go like this:
await Firestore.instance.collection("myDB/EG/EG/Cairo Governorate/Alexandria Governorate").limit(10).getDocuments().then((snapshot)async{
print(snapshot.documents);//[]
});
and like this:
await Firestore.instance.document("myDB/EG/EG/Cairo Governorate/Alexandria Governorate/<EXISTING DOCUMENT ID>").get().then((snapshot)async{
print(snapshot.data);//null
});
Such queries might be successful if any of documents still exist. As all documents in your screenshots are in Italic font I suppose that only references to some empty subcollection left. So you will not be able to query anything.
I hope it will help!

Related

Firebase rules acting very strange

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.
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.
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.

Firestore security rules get field/id of reference

I have two collections - tenancies and users.
A tenancy doc has a field called "landlordID" and is of type REFERENCE (not String).
Now in my Firestore Security Rules I want to allow a tenancy to be updated ONLY IF the landlordID field of that tenancy matches with the uid of the user making the request, namely request.auth.uid.
Read it as " allow a tenancy document to be updated if the user making the user is authenticated, hence request.auth.uid != null, and the landlordID field's ID should be equal to that of the request.auth.uid.
Hence the code should me something like this:
service cloud.firestore {
match /databases/{database}/documents {
match /tenancies/{tenancyID}{
allow update: if request.auth.uid != null &&
request.auth.uid == get(resource.data.landlordID).id
}
}
I have also tried get(/databases/$(database)/documents/users/$(resource.data.landlordID)).data.id
Supporting screenshot of my database
This should be very simple but get() simply does not work. Firebase Docs, scroll to "Access other documents" was not helpful at all for my situation and I am not sure how to get it working.
It would be a shame if references can't be used like this as they are just like any other field of a document.
Here is a function I made that works for me. I guess you have a user collection with users having the same id as their auth.uid
function isUserRef(field) {
return field in resource.data
&& resource.data[field] == /databases/$(database)/documents/users/$(request.auth.uid)
}
Adjusting to your use case you'd call the function so: isUserRef('landlordID') although the ID at the end of it is a bit misleading as this field is in fact a reference.
I see a couple of issues here. A first problem is that the get() function expects a fully specified ducument path, something like:
get(/databases/$(database)/documents/users/$(resource.data.landlordID)).data.id
A second problem is that you are trying to use the reference type in your rules, I do not think that is possible unfortunately.
The reference type in Firestore is not very helpfull (yet), I think you should store the landlordID as a string, then you can simply do something like:
service cloud.firestore {
match /databases/{database}/documents {
match /tenancies/{tenancyID}{
allow update: if request.auth.uid != resource.data.landlordID;
}
}
I had the same issue I needed an answer for. See this Google-thread with the answer from someone from google. To quote it:
You can get an id out of a path using the "index" operator:
some_document_ref should look like /databases/(default)/documents/foo/bar
which has 5 segments: ["databases", "(default)", ...]
some_document_ref[4] should be "bar"
allow create: if request.resource.data.some_document_ref[4] == "bar";
You can also use the normal get and exists functions on them.
A few difficult aspects of this that you may run into:
There's no way to retrieve the number of segments in a path at the moment (we're adding this soon), so you'll need to know some information about the reference ahead of time
There's not great support for writing references using the simulator in the Firebase Console. I used the Firestore emulator to test out this behavior (gist1, gist2)
might be too late, but I was able to piece together (despite a lack of docs) that a document reference is just a path, and complete path can be created with
/databases/$(database)/documents/users/$(request.auth.uid)
Then I have an array/list in firestore of references, called reads that I can grab with:
get(/databases/$(database)/documents/users/$(userId)/userinfo/granted_users).data.reads
Leaving me able to create a bool, and a rule with:
/databases/$(database)/documents/users/$(request.auth.uid) in get(/databases/$(database)/documents/users/$(userId)/userinfo/granted_users).data.reads
obviously your data structure will vary, but knowing the ref is a path is the important part here.
I had to experiment a little to get this working. Here the function that worked for me
function isUserRef(database, userId) {
return 'user' in resource.data
&& resource.data.user == /databases/$(database)/documents/users/$(userId);
}
And I call it like:
match /answers/{answer} {
allow read:
if isUserRef(database, request.auth.uid);
}
As mentioned by some other answers, a reference has a path property that is just a string that will look something like users/randomuserid123. You can split that into an array and match it against the user making the update request.
...
match /tenancies/{tenancyID}{
allow update: if request.auth.uid != null &&
resource.data.landlordID.path.split('/') == ['users', request.auth.uid]
}
...
Also had a trouble handling this problem, but in my case I needed to allow the user to add a message into a chat only if they're the owner of that chat room. There are 2 "tables" - chats and chat_messages, and chat_messages relate to a specific chat through chatId field. chats objects have ownerId field.
The rule I've used goes like this:
// Allow adding messages into a chat if the user is an owner of the chat room
match /chat_messages/{itemId} {
function isOwner() {
return get(/databases/$(database)/documents/chats/$(request.resource.data.chatId)).data.ownerId == request.auth.uid;
}
allow read: if true;
allow create: if isOwner();
}

Firebase rules - Simulator says yes, code says no

I'm working on a Flutter app using Firebase as a backed. I've set up group based roles in Firebase and the rules simulator in Firebase tells me the user I'm testing has access to the document. When I do a query in my Flutter code, I can see it finds the document and I can see it for a split second before it changes it mind and I get a "Listen for query at students failed: Missing or insufficient permissions." and the document is removed from the snapshot.
The query I use in the Flutter code is as follows:
Firestore.instance.collection('students').where('test', arrayContains: userID).orderBy('name').snapshots()
I have been playing with the document and tried different approaches for the current user to query for the document, and just to test it out I created an array with the userId and look for that.
If I completely skip the rules and just put the "need to be logged in" as requirement then I get a document back but as soon as I use the role based one then it's back to the drawing board. The rules I've set up are:
service cloud.firestore {
match /databases/{database}/documents {
match /students/{student} {
function isSignedIn() {
return request.auth != null;
}
function getRole(rsc) {
return rsc.data.roles[request.auth.uid];
}
function isOneOfRoles(rsc, array) {
return isSignedIn() && (getRole(rsc) in array);
}
allow read, write : if isOneOfRoles(resource,['teacher', 'student', 'parent']);
}
}
}
Any idea what's causing this?

Firestore; security rule simulation passes but actually fails (uid as map key)

I can't understand why this Firestore security rule fails in web.
I'm using #angular/fire latest with a query on a collection and with firebase latest, but it works in simulation.
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'cities' collection
match /events/{event} {
allow read: if (resource.data.access.code == 'public' || (resource.data.access.code == 'protected' && resource.data.objname.objfield == "A"));
}
}
}
Here's the data:
If access.code is "protected", then we look at objname.objfield == "A".
Checking access.code == "protected" lets me access the data, but the second part objname.objfield == "A" doesn't.
I don't understand why.
I've made sure this property exists in all objects of the collection. (Firestore security rules based on map values)
I tried several different ways, they all pass in Simulation, because it's a single document query.
An actual query at collection level does not go through permissions with the actual web call.
This is the query I'm making, which passes when there's no security rule in place
const pathAccessCode: firebase.firestore.FieldPath = new firebase.firestore.FieldPath('access', 'code');
const eventsUser: AngularFirestoreCollection<any> = this.firestore.collection('events', ref => ref.where(pathAccessCode, '==', 'protected'));
const eventsUser$: Observable<any> = eventsUser.valueChanges();
eventsUser$.subscribe(data => console.log(data) );
As you can see; some properties are recognised by the rule (access.code) but not others (objname.objfield).
The only think I can think of at this stage, is that access.code has been created before I started playing with the rules.
objname.objfield however, was created after I started using rules.
Can this be a thing?
Thanks for your help
After asking Firebase support, I was told that in the context of queries, the security rules have to match the query themselves.
That is; if I want to query documents with filters on fieldA and fieldB, then only fieldA & fieldB should be in the security rule.
This is not what I want to achieve here however.
Hope that helps anyone with the same issue.

Security Rules for Admin Users and Creators

I can't understand exactly how it work to create my rules for Firestore.
So far what I've tried with the help from the doc,
I created a document called users with a user that have my UID.
Then I created a field named admin
I've set it to true.
Now my rule look like this :
service cloud.firestore {
match /databases/{database}/documents {
match /trainings/{$tId} {
allow read, write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
I fetch my data in javascript
this._db
.collection('/trainings')
.get()
.then(itemsColl => itemsColl)
That don't work. My next step will be to add a created_by field with the UID on each of my training to get the creator uid stored and add a rule so they can read/write them training stuff.
Someone can help me getting that first rule working and tell me if my next step look in the right way ?
Thanks!

Resources