How to Compare Dates In Firestore Rules From Database And Request - firebase

Recently I Was Working On A Platform Where I Came Across A Problem Where I Want To Compare A Timestamp Stored In The Users Database And The Time Of Request In Firebase Rules. This Is So That One Can Read The Document Only If He/She Requests Only After A Certain Time.
Here Are The Codes I Tried To Use So Far.
Note: The Fieldname strton refers to a Time Stamp Object In The Document Of The Database.
match /tests/{testsID}{
match /response/{responseID}{
allow read: if get(/databases/$(database)/documents/tests/{testID}).data.strton < request.time;
}
}
match /tests/{testsID}{
match /response/{responseID}{
allow read: if get(/databases/$(database)/documents/tests/{testID}).data.strton.toMillis() < request.time.toMillis();
}
}
Any Help On How I Can Achieve This Is Appreciated.
Updates:
Here's The Document Structure(Private Info Has Been Censored)
And The Code Which Is Trying To Access It
docRef = doc(db, "tests", testid,"responses",auth.currentUser.uid);
docSnap = await getDoc(docRef);
if (docSnap.exists()) {
testResponseList = docSnap.data()
}else {
await setDoc(doc(db, "tests", testid,"responses",auth.currentUser.uid), {
answers:[]})
One More Thing Is That The Document Which This Code Is Trying To Get Doesnt Exist Right Now. And The Else Statement Is For Making The Document If It doesnt Exist. But the else statement is not executed.

You are using the {wildcard} expression syntax that is used in document path with /match in get(). Instead you should be using $(testsID).
Also there are a few spelling errors:
Your collection name is responses but you have response in rules
It is testsID in wilcard but you have testID
Try refactoring the rules as shown below:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tests/{testID} {
match /responses/{responseID} {
allow read: if get(/databases/$(database)/documents/tests/$(testID)).data.strton < request.time;
}
}
}
}

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.

Firebase query, rule for collectiongroup query getting in the way

Just when I thought I had the hang of it, the query rules throws me a curve ball :(
I have this query rule:
// Needed for collection group (Member) query
// https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html
match /{rootPath=**}/Members/{member} {
allow read: if request.auth != null;
}
It's pretty basic, only needs an authorized user. collectiongroup query works perfectly as expected.
Now, I want to have another query just to get member documents:
Firebase.firestore.collection("Companies\\$companyID\\Members").get().await()
The query returns an error (PERMISSION_DENIED).
I also tried adding a rule just for members like this:
match /Companies/{companyID} {
allow read: if request.auth != null &&
isMember(database, companyID, request.auth.uid)
match /Members/{member} {
allow read: if request.auth != null
}
}
Still, the same error.
This is the document path:
I looked at a few resources, but I didn't see anything to suggest a solution:
Understanding Collection Group Queries in Cloud Firestore
Recursive wildcards
I am posting this as an answer, as it is too long for comment.
Have you tried the following example rule:
service cloud.firestore {
match /databases/{database}/documents {
match /Companies/{companyID}/Members/{member} {
allow read, write: if <condition>;
}
}
}
as mentioned earlier in the documentation you shared based on structuring rules with hierarchical data?
I would recommend that you have a look at the following documentation where you can find some examples:
service cloud.firestore {
match /databases/{database}/documents {
match /Companies/{companyID}/Members/{memberID} {
// Only authenticated users can read
allow read: if request.auth != null;
}
}
}
Using the above security rules, any authenticated user can retrieve the members of any single company:
db.collection("Companies/{companyID}/Members").get()
Now , if you would like to have the same security rules applied to collection group queries, you must use version 2:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can query the posts collection group
// Applies to collection queries, collection group queries, and
// single document retrievals
match /{path=**}/Members/{memberID} {
allow read: if request.auth != null;
}
}
}
Any authenticated user can retrieve the members of any single company:
But what if you want to show a certain user their members across all companies? You can use a collection group query to retrieve results from all members collections:
var user = firebase.auth().currentUser;
db.collectionGroup("members").where("author", "==", user.uid).get()
Note: This query requires will require a composite index for the members collection. If you haven't enabled this index, the query will return an error link you can follow to create the required index.
You can try using "match /{path=**}/Members/{member}" instead of rootPath. I have not used the latter but the former worked for me in other projects.

Firebase - Rule always denies requests

I am starting to restrict the access to my Firebase Database. So far I have three collections where I simply want to return true for testing purposes.
I did it like this
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /devices/{device} {
allow read, write: if true;
}
match /users/{user} {
allow read, write: if true;
}
match /groups/{group} {
allow read, write: if true;
}
}
}
When I try to test this, I can't access the data no matter what. It gets always denied.
Your rules are correct, I see the issue is how you are using the playground,
In the textbox just enter something like
/devices/yourdeviceId
When you use the console simulator to test your rules, the field for the document to test should only contain the path of the document to get, using only the names of specific collections and document IDs. If you're trying to test document "foo" in collection "devices", then your path should be "/devices/foo". Don't enter the full match path, and don't use any wildcards - you should refere to an actual document in the same way that you would refer to it in your app code.

How to use Firebase Rule exists in SubCollection in Firestore

I'm currently working on Firebase Rule and I'm also a newbie to it. I am using exists and it seems it doesn't work, probably my syntax or query isn't correct. Any Idea how it should work? I'm trying to check the document data that I encircled in red if it exist.
Edit : It seems I'll be using get() and not exist to extract the uid in the File Path base on the suggestion, my question is how to make the File Path that the field I encircled for me to use it for the condition in Firebase Rule.
Client Code
return Firestore.instance
.collection('contacts')
.document(uid)
.setData({
friendId: {
“message”: {
},
“uid”: friendId,
}
});
Firebase Security Rules
match /databases/{database}/documents {
match /contacts/{userID}/{friendID} {
allow read,write : if isValidToAccessContacts(userID,friendID);
}
}
function isValidToAccessContacts(userId,friendID){
return exists(/databases/$(database)/documents/contacts
/$(request.auth.uid)/$(friendID.data.uid)/{document=**}) ||
exists(/databases/$(database)/documents/contacts
/$(friendID.data.uid)/$(request.auth.uid)/{document=**}) ||
exists(/databases/$(database)/documents/contacts
/$(request.auth.uid)/$(request.auth.uid)/{document=**})
;
}
You can't use a wildcard like this {document=**} using exists. You must call out the full path of the document to check for existence.

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?

Resources