How do i write a Firebase Storage security rule that only allows write file permissions for auth users to a nested directory and none its parents? - google-cloud-storage

I have the following security rule set for a Firebase Storage bucket:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /avatars/{userId}/{profileId}/{allPaths=**} {
allow write: if request.auth != null && request.auth.uid == userId
}
}
}
This works assuming the dynamic subdirectory userId matches the request.auth.uid.
However, this rule also allows that same user to write a file to the subdirectory {userId} and to write additional directories into profileId.
Id like to allow the authenticated user to generate both of the wildcard subdirectories as i have specified, but ONLY allow FILES to be uploaded into the last directory: profileId.
So...
attempting to upload a file into /avatars/${userId}/{profileId}/file.jpg should succeed.
attempting to write a file into /avatars/${userId}/file.jpg should fail.
attempting to create a directory in /avatars/${userId}/{profileId}/directoryName should fail.
attempting to upload a file into /avatars/${userId}/{profileId}/directoryName/file.jpg should fail.
Is this not possible?

Firstly, it's important to understand that Cloud Storage does not have "directories". There is no operation to create a "directory". There are only objects with paths that can have / separators to make it easier for you to organize content.
The reason why users can write to nested paths under profileId is because you are using a trailing recursive wildcard match {allPaths=**}, which allows writes under any possible path under profileId. I suggest reviewing the documentation on wildcards to better understand the behavior. It sounds like you don't want a recursive wildcard match at all, and instead just a single path segment match, e.g. {imageId}.

Related

How to detect forward slashes in Firestore document IDs with security rules?

I have a usernames collection. Each of its documents has an id equal to the username of an app user, and a single field userId, which is a document ID in a parallel users collection. If a user changes his name to John52, I create a new document with an ID John52, delete the old username document, and update the username field in several other documents in other collections using a batched write.
I would like to prevent the creation of certain usernames, and I use the following security rule to achieve that:
function isSignedIn() {
return request.auth.uid != null
}
function existingData() {
return resource.data
}
match /usernames/{username} {
allow get: if isSignedIn();
allow create: if username.size() >= 5 && username.size() <= 12 &&
username.matches("[[:alpha:]]*") == true &&
username.lower().matches(".*duck.*") == false;
allow delete: if isSignedIn() && request.auth.uid == existingData().userId;
}
It is supposed to allow the creation of a new document only if its ID is: (1) between 5-12 characters in length, (2) alphanumeric, and (3) not a bad word.
The problem is that this rule does not prevent usernames with forward slashes at the beginning or the end of the otherwise valid text, e.g., usernames /John52 and John52/ are both allowed. The document ID that I can see in the Firebase console is still John52 (without slashes), so I suspect that perhaps Firebase somehow treats those slashes in a special way, or drops them, and the {username} in the security rule has no slashes anymore.
I know that document IDs should not contain forward slashes. However, if the ID gets set from the client, what is the best way to prevent it, and is it possible to do with the security rules?
However, if the ID gets set from the client, what is the best way to prevent it, and is it possible to do with the security rules?
First, the Firebase client SDK will remove the slashes from the document ID before that create/update document request is sent so value of username will never contain /.
If someone tries to use the Firestore REST API and bypass the SDK, they'll get an error says:
{
"error": {
"code": 400,
"message": "Resource id \"/test\" is invalid because it contains \"/\".",
"status": "INVALID_ARGUMENT"
}
}
The best you can do is replace / with any other character (e.g. =) but render it as a slash on frontend (i.e. .replace('=', '/')).
Alternatively, you can store all the username in an array but a document only has 1 MB max size limit and won't scale well. Routing write requests through a Cloud Function would be another option where you can encode the username into a base64 string and use it as document ID. You can then decode it while rendering.
The client SDK will remove only trailing or leading slashes.
const docRef = doc(db, 'books', '/test/')
console.log(docRef.id) // <-- 'test'
await setDoc(docRef, { ... })
If you have multiple forward slashes and create odd number of path segments, then it'll throw an error as the path would represent a collection and not a document.

User can't write to Firestore database even though he is authorized

I created a rule in Cloud Firestore to read/write based on wether the user is signed in through Firebase Auth or not.
From my understanding based on what I read in the official documentation, the following code should allow the signed in user the correspodent permissions to the userID document inside the data collection.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/document {
match/data/{userId}{
allow read, write: if isSignedIn();
}
}
function isSignedIn(){
return request.auth != null;
}
}
Database image:
The idea is that after the user logs in, the code I wrote should verify if there is a document called ReservedID in data/userID/ReservedID, and if there isn't, create one for him, however, this collection is never created.
It does work if I remove the security rules.
Image of the error that shows in Android Studio:
However, after signing in using mAuth.signInWithEmailAndPassword, the user still can't write or read from the database. The Android Studio Logcat provides this message:
PERMISSION_DENIED: Missing or insufficient permissions.
Did I misunderstood how to properly set these rules in my database? Or could it have something to do with the code itself?
It looks like there are some issues with the code.
The match statement should specify the path to the collection and whole documents, rather than just one document. i.e you are using the path for single document match /databases/{database}/document instead of below path:
match /databases/{database}/documents {
match/data/{userId}{
}
With the above changes, the code will look like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match/data/{userId}{
allow read, write: if isSignedIn();
}
}
function isSignedIn() {
return request.auth != null;
}
}
You can verify this on playground

unable to download images form firebase storage [duplicate]

I have a function that successfully uploads an image to my cloud storage bucket.
With another function I want to get the URL of the image to show it on my page (With <img.../>)
getImageUrl(id: string) {
return this.storageRef.child('X/' + id ).getDownloadURL();
But when I do this.. I get an 'invalid' URL and when I copy the URL and go to it, I get the following message:
{
"error": {
"code": 403,
"message": "Permission denied. Could not perform this operation"
}
}
I read somewhere that this might be because there is no token attached in the URL, but how can I enable this?
The last couple of days I have been trying to understand Firebase Storage rules and I don't know why but when I separate rules for writing and for reading like this for example:
allow write: if request.auth != null && request.resource.size < 3 * 1024 * 1024;
allow read: if true;
the code works great and I can write and read using getDownloadURL(), but when I use them together like this:
allow read, write: if request.auth != null && request.resource.size < 3 * 1024 * 1024;
I got the same error as you:
{
"error": {
"code": 403,
"message": "Permission denied. Could not perform this operation"
}
}
I can write when using them together, but when I try to read the file using getDownloadURL(), the issue appears. Maybe you could try separating the rules as I mention and see if it works. I hope it solves your problem. Also, don't forget that the rules are live after 5 minutes from the moment you set them. Good luck.
You have to set Storage Security Rules, read more about this here
Just faced similar issue with my project, if anyone is still struggling with it, you have to provide proper rules for your Cloud Storage from the Firebase console.
Checkout this link to get full detail of the rools.
If you are uploading any object on your storage, you will require to add write rule under Firebase console > Storage > Rules.
I've faced the same issues with access to my storage files in my iOS project. I don't have any custom security rules. Just what's default in configuration. Breaking rules to new lines helped!
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow write: if request.auth != null;
allow read;
}
}
}
I don't know if this is some firebase bug but it helped:)
just use this rule for image
service firebase.storage {
match /b/{bucket}/o {
match /images/{imageId} {
// Only allow uploads of any image file that's less than 5MB
allow write: if request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*');
}
}
}
Separating read and write in different lines makes the issue go away, plus in your url you can just append ?alt=media to render it on screen.
allow write: if true; //your conditino
allow read: if true;
You need to use the getDownloadURL method. This will allow you to obtain the URL needed to download the image or for reference in your own html.
See the reference documentation below:
https://firebase.google.com/docs/storage/web/download-files#download_data_via_url
For my case, I accidentally delete the files before so it needs to show the File not found message but don't know why it was showing the Permission denied message.
Edit:
Although this was a temporary fix but it comes with security vulnerability (refer to #Joao Gavazzi's comment).
I was able to solve this by changing the FirebaseStorage security rules from default to:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}

can't read path variable in firestore rules

I have some rules like:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /teams/{teamId} {
allow read, write, delete, list:
if debug(teamId) in debug(request.auth.token.teams)
allow create:
if true;
}
}
}
I am using claims to control access. Each user has an array of teams in their token. That part is working fine.
Basically, teamId (coming from the path) doesn't exist. Nothing is printed in the log for this variable. I can't figure out whats going on. Is there some different way to access that teamId variable?
by doing some logs, it seems that for accessing /teams/teamXXXX I'm getting multiple rule hits. First on /database/default/documents/teams and then again on /database/default/documents/teams/teamXXX The first rule pass is failing because {teamId} is not defined on that path. I need to somehow allow access to the collection while limiting access to the child documents
It looks like the way I'm accessing teams may be causing a problem. I'm getting teams by doing a query like:
instance.collection("teams").where('owners',
arrayContains: FirebaseAuth.instance.currentUser!.uid);
This must be triggering the rule check where no {teamId} in the path. I thought about wrapping my match statement like:
match /teams/{document=**}
allow read:
if (request.auth.uid != "")
match /teams/{teamId} {
allow read, write, delete, list:
if debug(teamId) in debug(request.auth.token.teams)
allow create:
if true;
}
}
I'm worried that this will just allow all documents. I'm stuck.
answering my own question.
When doing a search / list on a collection, it still matches the rule of /teams/{teamId} even though you are not specifically querying by teamId.
Meaning a search like instance.collections("teams").where(...) will still match /teams/{teamId}
In the match, {teamId} will be blank. Instead, you must look at the input paramters coming in via the "resource" variable. The items you have in the "where" clauses will appear in the resource variable. You must use the resource data to resolve your rules.
So I had to separate the "list" rule form the rest of the rules.

Meeting permission error on my Firestore rule accessing other user's path

I'm testing permission for accessing user A's document from login user B.
User B already saved uid_A on it's path /userData/uid_B/subscriber/uid_A.
In this situation, I'd like to access A's document by checking subscriber in Firestore rule.
service cloud.firestore {
..
match /databases/{database}/documents {
match /userData/{uid}/{document=**} {
allow create, read, update, delete: if exists(/databases/{database}/documents/userData/$(request.auth.uid)/subscriber/$(uid));
}
}
}
But following Flutter code fails with this message:
6.33.0 - [Firebase/Firestore][I-FST000001] Listen for query at userData/uid_A failed: Missing or insufficient permissions.
await db.collection('userData').doc('uid_A').get();
The reason user B want's to access user A's document is to add uid_B as user A's provider, so user A can easily list up his accessible documents on screen.
I wonder what's causing this issue and how to resolve this kind of problem.
Thanks for advance.
Your syntax for the document to check with exists() isn't correct. The full path includes more, as shown in the documentation:
exists(/databases/$(database)/documents/userData/$(request.auth.uid)/subscriber/$(uid));