Deny read on unpublished posts firebase same collection - firebase

I have a simple fireStore collection named jokes where there is a document per joke.
In a joke, there is a key published: boolean
So the idea is to have a single collection with jokes, but each document can be either published or unpublished. I would not like users to view unpublished jokes.
In my fireStore rules, i have the following:
match /jokes/{id} {
allow read: if get(/databases/$(database)/documents/jokes/$(id)).data.published == true
}
In my application, I want users to be able to see only the published jokes, therefore i use the where
this.$fireStore.collection('jokes').where('published', '==', true).get()
When I do this, the console tells me i got insufficient permissions.
Is it possible to use this pattern, or do I have to use cloud functions to serve the published jokes? Or maybe a separate collection for unpublished ones?

You don't need a get() in the rule for this, as the current document is already available as resource. In fact, that get() is the problem here, as the rules engine cannot statically evaluate that for all document in one go.
match /jokes/{id} {
allow read: if resource.data.published == true
}
Also see the documentation on securely querying data, specifically the section on securing and querying documents based on a field

Related

Why isn't my Firestore Security rule cascading downward?

I can't figure out how to write a Firestore rule that reflects: "when the user requests a doc from the messages collection, check that the doc above that "messages" collection includes a field thats an array which includes their uid. I've gotten the rule to work on the top level but if I try to access a document inside a collection, which is inside the "chats" collection, the user is denied.
Here's my relevant rule:
match /chats/{chatId}/{allChildren=**} {
allow read, update: if request.auth.uid in resource.data.usersUids;
}
This works (the chatId doc has an array of uids):
await db.collection("chats").doc(chatId).get()
This does not work (none of the messages have an array of uids):
await db.collection("chats").doc(chatId).collection("messages").get()
It seems like the heart of the issue is that I'm trying to get the resource.data of the messages collection rather than the resource of the chatId doc, but I can't figure out to solve this.
resource only contains the data from the specific document that was matched by the entire path. It doesn't contain anything from any parent documents that appear in that path.
Any time you want to use fields from a document that wasn't matched by the full path, you have to get() the document using its own full path as described in the documentation. For example:
match /chats/{chatId}/{allChildren=**} {
allow read, update: if request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.usersUids;
}

Firestore - disallow reading multiple documents at once

I've got a Firestore collection.
The IDs of the documents are secrets. You should be able to read only the document whose ID you know.
For the sake of simplicity. I'd like to stick to this approach.
However, by default, one can read an entire collection from Firestore, for example
await firestore.collection("secret_documents").get()
Is it possible to allow reading only one document at once, only when it's referred by its ID?
Yes, that is actually quite easy. To control what documents can be accessed, use Firebase security rules for Firestore.
By default your security rules will be read and write, but those can actually be broken down into more granular operations of get, list, create and update. And what you're trying to do is to allow get, but not a list operation. From the documentation:
service cloud.firestore {
match /databases/{database}/documents {
// A read rule can be divided into get and list rules
match /cities/{city} {
// Applies to single document read requests
allow get: if <condition>;
// Applies to queries and collection read requests
allow list: if <condition>;
}
...
So to allow get for everyone and disallow list calls:
allow get: if true;
allow list: if false;
You'll probably want to elaborate on the allow get rule a bit, because it's more common to restrict it, for example to users that are signed in to your project with Firebase Authentication:
allow get: if request.auth.uid != null;
https://firebase.google.com/docs/firestore/security/rules-query

Firestore auth rule to test reference value in list

I'm trying to create a Firestore auth rule that checks the current user against a list of team members in a team document. The members are stored as document references so I've been trying things like this:
match /teams/{document=**} {
allow read: if path("/users/" + request.auth.uid) in resource.data.members;
}
But when I try and access the team document I get told there is an Auth failure.
Each team member has their own document in /users using their UID as a key. So a user might be /users/12345678 and the teams document might have:
/teams/team1 {
members: [/users/12345678, ....]
}
Where the members are Reference types.
So far I've not been able to figure this out as Firestore does not seem to have the concept of a document reference type in it's auth rules.
Any suggestions?
Security rules do have a concept of a reference, and it's represented as a Path type object. When a document reference is read by security rules, you have to treat it like a Path. And that Path will be fully qualified like this:
/databases/$(database)/documents/collection/documentId
Where $(database) comes from your usual top-level database wildcard match.
So, your rule might be implemented like this:
match /teams/{document=**} {
allow read: if /databases/$(database)/documents/collection/users/$(request.auth.uid) in resource.data.members;
}
Note that in security rules, you can build a path simply by starting with a /, and use $(foo) for interpolating variables as path components.

Do I need to do a get for every field of a document I want to access

I see that to get a field of a document in security rules one must use get. The example below shows getting the 'admin' field of some document in the users collection. If I wanted to get another field, would I have to do another get request or can I just do one get request and get all the fields I need in the document.
Here is the example I'm referring to in the documentation.
https://firebase.google.com/docs/firestore/security/rules-conditions
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
Yes, you would have to write another get(). There are no variables in Firestore security rules, so you can't store the contents of a get() in order to use its data multiple times.
Multiple gets accessing the same document might not incur multiple read charges. The documentation states:
Some document access calls may be cached, and cached calls do not count towards the limits.

In Firebase Firestore, is there any way to pass info to the Security Rules which is not part of the path?

I would like to send some info to Firestore database (Firebase), preferably in key-value pairs (but not necessarily), so that it can use it to evaluate access in their rules (both when reading and writing).
However, I don't want this info to be part of the path.
For example, suppose I had some passParameters method:
DocumentReference docRef =
db.collection("cities")
.document("SF")
.passParameters("abc", 123);
Then I could access this info when writing rules, like so:
service cloud.firestore {
match /databases/{database}/documents/cities/SF/ {
allow read, write: if request.parameters.abc == 123;
}
}
Please note, the above is just an example. Real-life uses cases are more complicated. In other words, don't pay too much attention to the example itself, but answer the more generic question: Is there any way to pass info to the Security Rules which is not part of the path?
You can send such parameters using custom tokens. Include those values as claims in the custom token, and use that token in your client when sending request to firestore (or signin).
This link explains how to-
1) create custom tokens, 2) include custom claims in those tokens, and 3) access those claims in the security rules.
You can have a cloud function to generate that custom token with custom claims for a specific user.
If the information you want to pass to firebase as parameter changes frequently, then this is going to be a cloud function call everytime you want to change the parameter value you are passing- so a bit costly. But if parameter tend to change less frequently (like- some role or special privilege that the user have), then this solution should work perfect and that's one of the primary benefits of custom token.
Even though it is not as simple as your example expectation snippet, still this I believe is one way to achieve what you want.
That's not supported. It wouldn't be a very "secure" security rule if the client could just specify whatever security parameters it wants with a query. That's really no different than allowing a client to pass a plaintext password that gives someone access to something. I would expect that sort of information to be discovered by an attacker.

Resources