Firestore security rules: get() use in hasAny() list method - firebase

I was wondering if this security rule would be possible:
function productForUser() {
return resource.data.products.hasAny(get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.products);
}
When I try to test it in the testing plaground on the Firebase website, it is sucessful. However, when I try to run it with Javascript, with this query, I get the read denied, with "missing or insufficient permissions":
query.where("products", "array-contains", productId);
I can confirm that the user has the array of products, containg the specific product that is being looked up in the query.
Thanks in advance.

Your rule works in the console simulator because the simulator only supports "get" type requests for a single document. It doesn't work for queries because security rules are not filters. The rule will not be evaluated for each and every document in the collection, as that would not scale well at all for very large collections. To specify conditions for queries, you will need to provide exact values to check from the client - you will not be able to use a get() to find other values.
If you want to test queries before publishing your rules, you should not be using the simulator, and instead use the local emulator to test code that actually performs a query.

Related

Determine whether a list of paths contains a dynamic path

I have a function in my firebase rules file userRolesIncludesPermission, this function checks a property on the user to determine whether that user has a certain permission on their role, it does so by comparing references.
This works as expected in the firebase rules file when used for security rules for firestore. Unfortunately this does not work as expected with "storage".
After research I found out that the problem is the comparison between the references from the permissions list under user.data.roles[0] and my custom made path.
Is there any better way to produce a path that can be recognizable using a list function?
Is it an unknown limitation of storage security rules?
Note that the function getRootCollectionDocRef works perfectly when I try to build a queryable path even in the same request (for example when I try earlier in the code to query the user)
Another note is that in the rules playground (console debugger) it works. But in an actual request from the website (dev, for now) it doesn't.
function getRootCollectionDocRef(collection, docId) {
return /databases/(default)/documents/$(collection)/$(docId);
}
function userRolesIncludesPermission(user, permissionId) {
return firestore.get(user.data.roles[0]).data.permissions.hasAll([getRootCollectionDocRef("permissions", permissionId)]);
}
I tried to use every available list function such as "in", "hasAny" etc.
I tried to build the path while converting it with path() function.
I tried to convert to a set.
I tried to use a hard coded non-dynamic path. It failed as long as I use a custom-made path.
However when trying to query the path with a fixed index (i.e firebase.get(permissions[1]) it worked. But this is an invalid practice. I have to make it dynamic, but the problem is that I can't loop over the list and convert the paths to strings on the fly.
Thanks in advance!!

PageInfo in Firestore not usable

I would like to use pagination in Firestore so I can save a query state and automatically allow users to submit a page token to start a query again. The challenge I am seeing with this is that Firestore does not offer a query token or page token to resume iteration. However, after looking through the docs for GoLang, it looks like there is an exported PageInfo() method that returns a token I am interested in and an unexported fetch method.
Is there a method to use the exported PageInfo() values to allow me to fetch a new set of documents using that existing token?
To use pagination in Firestore Database you can use query cursors with limit() method as mentioned in this document. You can go through this youtube link to know more about it.
You may also consider using pageSize & pageToken query parameters and nextPageToken field with Firestore REST API as mentioned in this document to achieve pagination. There is a similar StackoverFlow thread which may help you.

Deny read on unpublished posts firebase same collection

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

Is there a way to make the ternary operator work for Cloud Firestore Security Rules?

In our Firebase setup we store the user's role in custom claims. And so, in our Cloud Firestore security rules we need to evaluate whether the user has the appropriate role to perform an action. So I created the function getRole which conveniently gets the requested value from the user's auth token. However, there are certain edge cases when the user does not yet have a role, and in those cases I want their role to evaluate to the lowest possible security role, in our case this is just "user". I read the language spec for CEL (Common Expression Language) which is what this rule language is based on and it does in fact support a ternary operator. (Doc). So I went and plugged in this code into my Firestore security rules and the online editor validated the rules and accepted my new rules. However, I later found that locally, when running my security rules unit tests and also loading up the rules in the firebase emulators, I get this error:
ERROR Use of ternary operator not allowed
So either the production Firestore rules support the ternary operator and the local emulators do not, or the production one is validating against CEL and passing validation when they should not.
At any rate, I would like to safely be able to have a function which returns the actual value of the user's role, or a default safe value if it is not set.
Please note that we have tried to omit the 'role' in getCustomClaims() statement and it blows up if the key does not exist in the custom claims.
function getCustomClaims() {
return request.auth.token;
}
function getRole() {
return 'role' in getCustomClaims() ? getCustomClaims()['role'] : 'user';
}
You can make this work without a ternary operator. You can see that, from the rules API documentation, that request.auth.token is a map type object. The API docs for Map says that there is a method called get() which accepts a default ("safe") value to return if the passed key isn't found:
getCustomClaims().get("role", "user")

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