Firebase rules not applying to subcollection documents - firebase

Here are my rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{id}{
allow read : if (request.auth.uid == resource.data.uid);
allow write : if false;
}
match /Class/{id}{
allow read : if (request.auth.uid == resource.data.instructor.uid || (request.auth.uid == resource.data.admin.uid));
allow write : if false;
}
match /Class/{id}/Topics/{doc} {
allow read:
if request.auth.uid == resource.data.topicOwnerUID || request.auth.uid == resource.data.adminUID
allow write: if false;
}
}
}
Everything is fine except trying to pull all the topics from Class/docs/Topics/docs. I use getDocuments and try to get all documents using limit(50), I only have 1 topic now, but I also tried making limit to 1 and still don't work.. In each of the Topics docs, there is an adminUID and a topicOwnerUID field. When the owner tries to pull all documents I am getting an error: "Error: Missing or insufficient permissions.". I checked and all the required fields are there and this should allow.
Edit: seems like if i remove .limit and just get a single doc with a specific docID it works. But this isn't what I want :/. Maybe I will have to make the subcollection into its own collection.
Edit2: doesn't work even if it is not a subcollection. completely lost rn. Looks like the only way is to get each doc individually but this ruins lazyLoading. Firebase is lagging behind. will be switching to another db.

Security rules do not mean anything without the matching queries - remember that Security Rules ARE NOT FILTERS - they will NOT "just give you the records that are allowed" - you MUST use queries to match your rules. If your query could return a document that isn't allowed by the rules, then the ENTIRE query is disallowed.
I can tell you from EXTENSIVE use that Firebase/Firestore do not lag behind in any way, and I use complex queries continuously.

Related

How to check auth uid with the uid present in the collection data

In firebase's firestore security rules:
I want to check the auth.uid with the uid data available in the document of the collection.
I tried using below code but it is not working....is there any way to get this done?
match /databases/{database}/documents {
match /candidates/{candidate}/{document=**} {
allow read: if request.auth != null && request.auth.uid == resource.data.userId;
allow write: if true ;
}
This is some improvement for your code. Check whether it would help to solve your problem.
Here in line 2 the syntax should be in the following format.
match /collection/document/collection
So because I think you referring to the documents in the candidate collection it should be as below if you don't want to the rule to be applied deep in the hierarchy.
match /candidates/{candidate}/ {
or as below if you need the deep hierarchy application of the rule.
match /candidates/{document==**}/ {
for the rule statements the first rule can be re-written as
allow read: if request.auth.uid == resource.data.userId;
since looking for the auth.uid in the request token will automatically block unauthenticated users since they won't have auth property on them.
For the second rule I guess you're using only for testing purposes. Since it is dangerous to allow anyone to edit documents without any permission. You can either same rule for the read operation or use a different rule to not allow unauthenticated write access.

Firebase "read" rules executing even when no document is found in a query result

In my Firebase Firestore Rules, I have the following rule for 'courses' collection:
rules_version = '2'
service cloud.firestore {
match /databases/{database}/documents {
match /courses/{courseId} {
allow read: if resource.data.userId == request.auth.uid;
}
}
}
The rules works ok if a read a single document using the firebase web client.
But when querying and the result is empty, for instance the following query:
const results = await app.firestore().collection('courses')
.where('userId', '==', 'unexistingUserId').get();
then the rules fail, throwing an exception on the client side (Missing or insufficient permissions).
Running locally on the emulator, I found that the problem was that the read rule was being validated against a resource containing the query field, even if with the resource not existing:
As shown above, the resource.data.userId contains the "unexistingUserId" value.
To solve this, I had to change the rules by adding validations to check if the resource exists:
rules_version = '2'
service cloud.firestore {
match /databases/{database}/documents {
match /courses/{courseId} {
allow read: if resource==null || resource.data==null || !('id' in resource) || resource.data.userId == request.auth.uid;
}
}
}
Now when querying for no results, the rule validates ok because the 'id' is not present in the request object:
So my questions are:
Is this the expected behavior?
Shouldn't the rules not even being validated if the query returns nothing?
Is there a way to change this behavior to avoid adding those checks in all my rules?
Is this the expected behavior?
Shouldn't the rules not even being validated if the query returns nothing?
This is the expected behavior. What are you are observing is because of the fact that security rules are not filters. Be sure to read and understand that linked documentation.
Queries work differently than single document get(). With a single document get(), the rules system doesn't have to worry about scaling up to match billions of documents, so it's perfectly reasonable to check the contents of the one document to see if it matches. However, since queries can return 0 or more documents, the rules are considered differently. They do not simply check each document that would result from a query. That wouldn't scale at all for very large collections.
Rules work with queries by checking that the filters on the client's query match the constraints in the rule. If you say that resource.data.userId == request.auth.uid, what you are expressing is that users may only ever try to query for documents where userId equals their own UID. They are not allowed to even attempt to match any other document in a query.
The only query that would ever pass this rule would look more like this:
const results = await app.firestore()
.collection('courses')
.where('userId', '==', firebase.auth().currentUser)
.get();
In this case, the filter:
.where('userId', '==', firebase.auth().currentUser)
Exactly matches the rule:
allow read: if resource.data.userId == request.auth.uid;
Any other query would be rejected immediately simply because it's trying to access documents that are known to violate the rule.

nested firebase-firestore rules: owner id within parent document

I can't get these rules to work:
I've got a collection with projects, which all have an owner. The owner should be allowed to read/write his projects and the subcollection working_copies as well.
This implementation succesfully grants reading the project, but fails (Missing or insufficient permissions) when reading a working_copy from the sub collection. I suspect it tries to find an owner within the sub-document.
service cloud.firestore {
match /databases/{database}/documents {
match /projects/{projectId} {
allow read, write: if
resource.data.owner == request.auth.uid;
match /working_copies/{doc} {
allow read, write: if true;
}
}
}
I've also tried using this condition either in the project path or in the working_copies path, but it both fails as well:
get(/databases/$(database)/documents/projects/$(projectId)).data.owner == request.auth.uid
Everything above also fails when i use a recursive wildcard for nesting:
match /projects/{projectId=**} {
...
The strange thing is, i think the first version used to work until some days ago.
I use angular/angularfire and call the requests like this:
this.db.collection('projects').doc('3279').collection<ProjectData>('working_copies').valueChanges().pipe(...
In the rules simulator it's green lighted though.
I've finally found a workaround:
!('owner' in resource.data) || resource.data.owner == request.auth.uid
This makes it accept that the child document doesn't provide the owner once more. So it seems, when nesting rules, the parent rules are also applied to child documents.

Firestore rules: How do I allow access to a subcollection depending on a field of the document that contains it?

I am writing a small program in react that manages a league of sorts.
The problem I have is how do I allow access (read, write) to the matchdays of a league for the correct user. The matchdays are named "matchday-1", "matchday-2", and so on, and are collections of documents (the matches), and are subcollections inside the league.
My firebase structure looks like this basically:
leagues(collection) -> league(document) -> matchday-n(collection) -> match-m(document)
where n and m are numbers.
Here's the issue:
The league document contains a field called "creator", which contains the ID of the user that created that league. Only they are supposed to access it and its matchdays!
But as it seems, when I am accessing a matchday, the value for resource.data.creator in the firebase rules is different from when I am accessing the league itself.
My question is: Which rule do I have to implement, so that only the user who created the league can access it and its subcollections?
I tried to find a way to compare the request.auth.uid to the creator of the league.
I tried something like this as a condition:
request.auth.uid == get("path-to-league").creator
, as you can see in the code I provided.
But it doesn't seem to work this way, as I might be referencing the path incorrectly.
This is my code at the moment:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /leagues/{league} {
allow read, write: if request.auth.uid == resource.data.creator
}
match /leagues/{league}/{document=**} {
allow read, write: if request.auth.uid == get(/databases/$(database)/documents/leagues/$(league)).creator
}
}
}
Depending on how I fiddle with the rule set, but in case of my provided code, I get "missing or insufficient permissions" when trying to access (read or write from) a league that has the wrong creator, which is good, but even if it's the correct creator, I cannot access the matchdays.
You're missing a bit of syntax. It should probably be this:
match /leagues/{league}/{document=**} {
allow read, write: if request.auth.uid ==
get(/databases/$(database)/documents/leagues/$(league)).data.creator
}
Note that there is a data property before creator. This gives you access to the raw field values of the document Resource object.

How does Firebase read / write rules affect user experience on queries?

I have read the Firebase docs, and am wondering how Firebase rules affect a user's experience within an app. Let's say you can only access a group if you are apart of that group as per your Firestore rules. So would you instead of querying all groups where member.uid == auth.uid ( like you would without rules), would you just query the "groups" collection, and the user would only receive the groups that they are apart of?
Every time I read about Firestore rules, they pretty much give the same example, without giving a sample of what the user would see if they queried the database. If someone could point me in the right direction of this, or show how I could test something like this, that would be much appreciated.
So would you instead of querying all groups where member.uid == auth.uid ( like you would without rules), would you just query the "groups" collection, and the user would only receive the groups that they are apart of?
Firebase rules cannot filters data. You cannot have the client read an entire collection and then expect the rules to return a subset of the documents in that collection.
The reason for this is that listeners/observers are only validated once you attach them, after that they have access to the data. This is necessary in order to get the realtime update performance acceptable.
While you can't use rules to filter data, you can use them to validate queries. For an example of this, see the Firestore documentation on securely querying data:
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth.uid == resource.data.author;
}
}
}
Which rejects this query:
// This query will fail
db.collection("stories").get()
But allows this one:
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
You can see that we're essentially repeating the condition from the code in the rules. This allows you to securely expose a subset of the documents in a collection based on (for example) the user's UID.
The firebase rules are there just to prevent spam in your application and also other stuff.
If you use read:true and write:true then anyone can read and write to the database, that is why it is better to use:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
This way only authenticated users can see the data that is being retrieved from the database or can send data to the database.
If you have a users in a group and you used this:
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'groups' collection
match /groups/{group} {
allow read, write: if true;
}
}
}
It means all members of that group can read from that database, so if you are in that group, then the data will appear in your application.
more info here:
https://firebase.google.com/docs/firestore/security/rules-structure

Resources