Security rule to limit storage space used in Firebase Storage? - firebase

In Firestore I store the infos of space used (in Firebase Storage) and space total per user and I was wondering if I could use these variables to write a Storage rule to directly limit storage space per user there?
Is there any way to achieve that? Is it even useful?
Currently the rule I have allow users to only access their own storage folder :
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{allPaths=**} {
allow read, write, delete: if request.auth.uid == userId;
}
}
}

What you're trying to do is not immediately possible in security rules. Storage security rules cannot access data in Firestore. Similarly, Firestore security rules cannot access data in Storage. So, it's not possible to stop a Storage upload from happening based on data that exists outside that product.
The best you can do to enforce a quota is to use a product like Cloud Functions to run a Storage trigger after an upload completes, and check at that point if the upload caused the user to exceed any limits. You can then choose to delete the file if you want. You might also want to use a trigger to maintain a running total of usage per-user.

Related

Trying to make app secure with API key, but it isn't being used

I've created a web application that displays data from my cloud firestore. I'm about to release it to the public, but I don't want just anyone to be able to read/write to my database.
I have currently restricted my API key to only allow requests from my website's url, but it doesn't seem to be doing anything. I've even deleted it from the app entirely and it is still able to access the database.
Is there a rule I need to set up in my firestore to make it require an API key? I've googled plenty of things, but all I can find are articles on why it's ok to have your key be available to the public.
It's not possible to restrict access to Firestore based on some plaintext API key or web site domain. If you're publishing an app that provides direct access to Cloud Firestore (or Cloud Storage, or Realtime Database), the only way to secure it is with a combination of Firebase Authentication and security rules. The security rules allow you to express who can read and write which collections and document.
If you aren't using Firebase Authentication, and your default security rules allow universal read/write access, then anyone with an internet connection will be able to read and write every document.
A slight variation on Doug's excellent answer is to allow all users to write to specific documents that pre-exist and that have impossible to guess names. These document names then essentially become your own API keys, that you share (out of band) with the users of your app.
The security rules for this can be as simple as:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow create: if false;
allow update: if exists(resource['__name__']);
allow get: if true;
allow list: if false;
}
}
}
So: anyone can get/update any existing document that they know the name of, but they can't create a document, nor get a list of all documents.
This prevents the need for using Firebase Authentication. On the other hand it means you can't lock down access on a per user basis. Any user that somehow gets access to the document name, can now read/write it at will.

Firebase rules security warning

I'm getting the following message in Firebase:
The Cloud Firestore "(default)" database of your project contains
rules that are not secure.
But I have the rules as the documentation says and I don't see any other option. What I want is that anyone can read, but just logged in users can edit content.
These are my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow read, write: if request.auth.uid != null;
}
}
}
With these rules any authentication user can write all documents in your database, including overwriting documents that were created by other. You're also allowing everyone (no matter if they're authenticated) to read all data in the database, which seem much broader than most apps need. While these are close to some of the default rules you can start with, it is typically not enough for a production app, which is why you receive the warning.
You'll typically want to further lock down access to the database. For example, you might want to ensure that users can only write documents that contain their own UID in a specific field. That way you'll always know who created a document, and can use that to control access to that document.
If you are certain this is the minimum data access that your app requires to function, you can disable the warning emails in the Firebase console. But as said before, in my experience this type of access seems much broader than what I typically see in well functioning, and well protected, apps.

Firebase Cloud Firestore restrict user access (Banking Application)

I am working on a banking app using firebase cloud firestore. I have already set the rules like so:
// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
My database is structured like this:
/consumers/{consumer_id}/transactions/{transaction_id}
the {consumer_id} will contain the account balance for that consumer along with other details and the {transaction_id} will contain the details about each transaction.
So if any authenticated user wanted to say, withdraw money they can do so using the android app/Web app. The problem is, can that same user access the database (eg: update their account balance) using their credentials with the REST endpoints without my knowledge? If so how do I prevent them from doing so?
There is no way to limit access to Firestore to just users who are using your app. Anyone who has the configuration data for your Firebase project, can call the APIs in that project. And as soon as you publish your app, you're sharing the configuration data with those users. So you'll have to assume that some malicious user(s) will at some point call APIs on your project without using your app.
For this reason you should enforce all business rules that you have in a trusted environment, such as your development machine, a server you control, Cloud Functions, or... server-side security rules. Since no user can access any of these, even if they run their own code, they'll be forced to adhere to your business rules.
Some examples:
Each transaction document probably contains the UID of the user who is posting that transaction, and of course users should only be able to post transactions with their own UID. You can enforce this in security rules with something like:
match /databases/{database}/documents {
match /consumers/{consumer_id}/transactions/{transaction_id}/ {
allow write: if request.resource.data.posted_by == request.auth.uid;
}
}
So now anyone (no matter if they're using your app or not) can only post transactions if that document contains their own UID. You'll probably want to verify a bit more, such as whether there is even a account document for them, and maybe whether you've verified their account in some way. All of these can typically be done from server-side security rules.
For more on this, see the documentation on accessing other documents in security rules, the pro-series video on building secure apps, and this video on security rules.
Since you keep the balance of each account in their parent document under /consumers/{consumer_id}, you'll need to update that document whenever a transaction is posted under it. While this is possible from within security rules, it's going to be quite involved. It's going to be easier to perform this update of the balance in server-side code.
A good solution for this is to run the code that updates the balance as a Cloud Function that gets triggered whenever a transaction is created (and/or updated if you allow that). Since this code runs in a trusted environment, you can be sure only you can modify it, and thus it can safely update the balance for the new/modified transaction.

How can access realtime database in storage security rules in firebase to verify if the user is in a group for special privilages?

I have a list of existing users in firebase.
I have storage files that should only go to users that are in a group "hasFullAccess" to access all the storage files.
Since we can't add on to the user object, I've added each of the users to the database to give them the extra metadata.
database:
{
users: {
$uid: { hasFullAccess: boolean}
}
}
Now in the storage rules I want to allow users that have full access to be able to access the full content.
service firebase.storage {
match /b/{bucket}/o {
match /files-demo/{allDirPaths=*} {
allow read: if true
}
match /files-complete/{allDirPaths=*} {
allow read: if request.auth != null && // (users have full access?)
}
}
}
If it's possible how can I reference my database in firebase storage rules?
You can't reference values from Realtime Database (or Firestore) from within Cloud Storage rules. Currently, each of these rule systems is completely independent of each other.
What you can do, however, is write Cloud Functions code that responds to changes in each one of these products that can also access the other products in order to check that values are consistent. A full exploration of this topic, and how to use Cloud Functions effectively this way, is outside the scope of a Stack Overflow answer.

Firebase Storage Security Rule : Check if another object exists / check object's metadata

In Firebase storage security rules (not realtime database), is there any way to perform a check if another object exists at path, or another object's metadata exists?
Some background
Currently my storage security rules are set up so that users only have read access, and not write access to their /users/{userId}/ paths.
I have an admin cloud function that saves a file to /users/{userId}/necessary-file.pdf. And I don't want users to be able to modify or write this file and only cloud functions to have the right to do. To achieve this I think I can match for the filename like :
match /users/{userId}/{fileName} {
allow write: if !fileName.matches("necessary-file.pdf")
}
Question
Is there any way for me to only allow users to write some-other-file.pdf if they already have a necessary-file.pdf at the same path (or even somewhere else if that works better). All while still disallowing them to write necessary-file.pdf.
So is there any way for me to do something like this pseudo-code? :
match /users/{userId}/{fileName} {
allow read: if request.auth.uid == userId;
allow write: if (!fileName.matches("necessary-file.pdf")) && ("necessary-file.pdf".exists())
}
As an alternative, I can have my cloud functions write a metadata to necessary-file.pdf and check for that too. is there any way I can perform something like this pseudo-code? :
allow write: if "necessary-file.pdf".metadata['canUserWrite'] == 'yesUserCan'
Finally
What's really cool about this is that, if this is in any way remotely possible, it can be used to communicate between firebase database and firebase storage rules in a not-so-realtime way. (referring to this question here) A cloud function can listen for changes in the intended field in realtime database, and write a file to firebase storage, which firebase storage can check for.
Firebase's Cloud Storage security rules can only access information about the current request and file. They don't have access to the full storage system, so can't check whether another file is present.
The reason for this is that the rules are evaluated in memory for every request. Providing access to Cloud Storage for other objects would slow the performance down, making the system unscalable. That same reason explains why you can't access the Firebase Database from the security rules.
If you want some control like this, you'll want to look in Cloud Functions for Firebase. If you have your users upload their files into a "staging" area, you can have a Cloud Function validate whether they met all prerequisites and only then move the file into the actual location (making it available for further processing or for clients to see).
(Another Solution) Restricting Storage Access with Auth Claims
Cloud Storage Rules has access to auth info for the request user. By setting up a check during the authorization process an auth property can be added for later access validation in storage rules.
Original Question:
Is there any way for me to only allow users to write
some-other-file.pdf if they already have a necessary-file.pdf at the
same path (or even somewhere else if that works better). All while
still disallowing them to write necessary-file.pdf.
Yes, this could be done by checking an auth.token.
Example flow for a Web App w/ Google Signin:
Create a Cloud Function to set a custom claim that checks for the existence of that file against the current auth user.
Upon success of the user authorizing via Google call that cloud function for it to set a value to the auth object.
Check auth.token in the storage rule.
Web Example for Custom Claims:
https://firebase.google.com/docs/auth/admin/custom-claims?hl=ro#examples_and_use_cases
Example Storage Rule:
https://firebase.google.com/docs/storage/security/rules-conditions?hl=ro#group_private

Resources