How to grant access to google cloud storage buckets based on users auth uid - firebase

I am running a cloud function that saves images like this:
//Pseudocode
const admin = await import("firebase-admin");
const bucket = admin.storage().bucket();
const file = bucket.file('myName');
const stream = file.createWriteStream({ resumable: false });
...
After the images are uploaded, I get the publicUrl like so
file.publicUrl()
and store it in an Object.
This object then gets stored to firestore.
When I now copy this url from the object(the url structure looks like this)
https://storage.googleapis.com/new-project.appspot.com/ZWHpYGSQWYXLlUcAwkRFQLC0u7s1/f2a48bdc-7eb3-4174-9f6e-3fd963003bd7/177373254.png
and paste it into a browser field am getting an error:
<Error>
<Code>AccessDenied</Code>
<Message>Access denied.</Message>
<Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage
object.</Details>
</Error>
even with the test rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if false;
}
}
}
I have read some issues here on stackoverflow and it seems like this is because I am using google cloud storage buckets and not firebase storage buckets (I thought they are the same)
but I am very confused about how to write rules in this case, so that only authenticated firebase users can read files.
Any help is highly appreciated.

The bucket is shared between Firebase and Cloud Storage, but the way you access the bucket is quite different.
When you access the bucket through a Firebase SDK, or through a download URL generated by a Firebase SDK, your access goes through a Firebase layer. This layer enforces the security rules (for the SDK), and grants temporary read-only access to the download URL.
When you access the bucket through a Cloud SDK, or a signed URL generated by a Cloud SDK, your access does not go through any Firebase layer, and thus Firebase security rules have no effect on this access.
The public URL you have is just a way to identify a file in your Cloud Storage bucket. It does not have any implied access permissions. You will need to make sure the IAM properties for your bucket allow the access you want the user to have to the file.

Related

Reading firebase storage image security rules

I am using Firebase storage and firestore with flutter,
I came across two options to retrieve Firebase storage image
Setting Firebase storage image url in firestore database and then fetching it with network image
Getting image url from Firebase storage directly
I don't know much about tokens.
My security rules states that only auth users can read my Firebase storage but if I use first option my image url with token is stored in my firestore database using that url anyone can access my storage. I am not sure does Firebase refresh it's storage token automatically then if this is the case my app will experience crash.
Which is the most secure and long lasting way or please answer if any other secure way to fetch images
Firebase storage tokens won't expire unless you revoke them. The token may update if you overwrite the image i.e. update it. Now that's totally your call if you would like to make a separate request just to get the download URL or store the URL in realtime database when an image is uploaded and fetch it along with other data.
Security rules of Firebase Storage will prevent non-authenticated users from getting the download URL only. If an authenticated user shares the URL with anyone, they will be able to see the image as they have the URL with that random token now.
If the data you are fetching from realtime database requires the user to be logged in at first place, then I'd just store the URL in the database itself as I don't think it makes sense to make another request and have the same rules for Firebase storage. I don't know your exact use case so there may be exceptions for doing this.
If you don't need that image URL always then that might be waste of bandwidth, then you should consider making separate request to get the storage URLs.
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
These rules will allow any authenticated user to request the URL. But as I mentioned earlier, anyone with this link can access the file.

Firebase Cloud Storage rules based on Firestore Data work around

In my firebase app I have
a list of users(all users are registered via Firebase Authentication )
a list of followers for each user
Each user has added a profile photo, the filename of the photo is unique and is saved in a firestore doc called "photoName" document
Only Followers of a user are permitted to read the "photoName" document ( as specified in firestore rules )
I want only the followers to be able to read the profile photo of the user, I have added a rule in cloud storage as follows:
match /profilePhotos/{profilePhotoFile_id} {
allow read: if request.auth.uid != null
}
The cloud storage rules are set on a file-level so is it accurate for me to assume that only the users who are logged in and are also a follower will be able to read the profile photo of another user? Unless they somehow magically guess the unique names of a photo.
Update dated September 28, 2022
Security Rules in Cloud Storage for Firebase now supports cross-service Rules and let you query your projects’ Firestore data, similar to the get() and exists() functions in Firestore Rules.
See this Firebase blog article and this SO answer from Puf.
Previous answer
Is it accurate for me to assume that only the users who are logged in
and are also a follower will be able to read the profile photo of
another user?
No, because it is possible, with the client SDKs to list all the files in a Cloud Storage bucket, as explained in the doc and your Cloud Storage Security Rules allow any authenticated user to read the profile photos files.
Also note that you cannot read Firestore documents when writing Cloud Storage Security Rules.
One possible approach is to use a Cloud Function to generate a signed URL that you store in the Firestore document AND to forbid read access to the profile photos files. Since Cloud Functions use the Admin SDK they can bypass the security rules.
The following Cloud Function code will generate a signed URL each time a file is added to Cloud Storage and save it in a Firestore document. With this signed URL anyone can read the profile photos file.
It's up to you to adapt it to your case by:
If necessary, only treating the profile photos (check that the file name contains profilePhotos)
Saving the URL in the correct Firestore doc: I guess the file name allows linking back to the user document. Also, you will probably have to change from add() to update().
exports.generateFileURL = functions.storage.object().onFinalize(async object => {
try {
const bucket = admin.storage().bucket(object.bucket);
const file = bucket.file(object.name);
const signedURLconfig = { action: 'read', expires: '08-12-2025' };
const signedURLArray = await file.getSignedUrl(signedURLconfig);
const url = signedURLArray[0];
await admin.firestore().collection('...').add({ signedURL: url })
return null;
} catch (error) {
console.log(error);
return null;
}
});
Two additional considerations:
You can use Custom Claims in Cloud Storage Security Rules, but it is not really recommended to use them for your case, see here.
You can also use file metadata in Cloud Storage Security Rules, but again it is not adapted to your case (you are not going to add followers Ids in file metadata each time a new follower registers...)

Do firebase storage rules override cloud storage permissions?

I have the following rules in my bucket:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read;
}
}
}
This says to me that public can read - but they can't. Checking on the google cloud storage UI, there is no 'public url'.
So a couple of questions:
Does firebase give a different URL for accessing the file which will issue permissions based on the above rule configuration?
There is a firebase-adminsdk service account in GCS bucket permissions - is that what firebase uses to access the bucket?
Which credentials overwrite each other?
​
Cloud Storage access permissions and Firebase's Storage rules are unrelated.
Firebase's Storage rules apply to users who access the data in the bucket through a Firebase SDK. In addition Firebase can generate a so-called download URL, which gives everyone who has that URL read-only access to that file.
Cloud Storage access permissions apply to users accessing the data in the bucket through a Cloud API. The Cloud Storage SDKs and APIs can also generate so-called signed URLs, which serve a similar role to the download URLs mentioned above.
Firebase security rules only affect direct access from web and mobile client apps when using the Firebase SDK, especially when the user is authenticated with Firebase Auth (as the user's account token is made available during the evaluation of rules).
Rule do not affect the way service accounts work, or other forms of public access that depend on Cloud Storage ACLs. Those are determined by their own permission systems. Those other systems don't also don't affect Firebase client access at all.

Firebase storage: check if user is admin

I have a folder in Firebase storage that only the admin service account should be able to write to. (More particularly, that admin service account will only write to the storage from a cloud function).
I'd like to figure out how to create a rule that prevents any other user from writing to the storage bucket. Does anyone know how one can accomplish this goal?
I thought I could create a rule that forbid writing unless the writing agent's uid matched the admin uid, but I haven't been able to find the admin uid yet. I tried logging into my service account like so:
import firebase_admin, os
from firebase_admin import credentials, initialize_app
if not len(firebase_admin._apps):
cert = os.path.join('src', 'secrets', 'service-account.json')
initialize_app(credentials.Certificate(cert), {
'databaseURL': 'https://womp.firebaseio.com/',
'storageBucket': 'womp.appspot.com/',
})
then digging through the firebase_admin._apps[0] object to see if I could dig out a uid for the user, but no dice.
If others know how to create a rule that prevents any but admin users from writing to a storage instance, I'd be grateful for any insights they can offer!
It's not possible to limit access to service account using security rules. Service accounts always bypass security rules.
Once you start working with service accounts, their access is controlled by Google Cloud IAM, which is completely different. You can use IAM to limit which service accounts are allow to access a bucket, and that's going to operate completely independently of whatever security rules you write for end users going through the Firebase SDK.
If you don't want any users to write directly to a bucket, and only allow service account, the security rules for the bucket should simply reject all access.
match /{document=**} {
allow read, write: if false;
}

Can Firebase Cloud Storage rules validate against Firestore data?

Can we use Firestore data to grant or restrict access to files hosted on Firebase Cloud Storage?
Exemple of what I would like to use as Firebase Security Rule
allow write: if get(/databases/mydbname/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"];
Update (Oct 2022): it is now possible to access Cloud Firestore from within your Cloud Storage security rules with the new firestore.get() and firestore.exists() functions. See the blog post Announcing cross-service Security Rules and the documentation on enhancing Cloud Storage security rules with Cloud Firestore.
Previous answer below for reference:
There is currently no way to access different Firebase products from within the security rules of another product. See: is there a way to authenticate user role in firebase storage rules?
But it seems like you are trying to check group-membership of the user. Instead of looking that group-membership up in the database, I recommend that you model it as a so-called custom claim. Instead of (or in addition to) writing the membership to the database, you'll set the claim "user {uid} is a member of group {guild1}" into the user profile using the Firebase Admin SDK:
admin.auth().setCustomUserClaims(uid, {guild1: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
With that done, you can check the guild membership in the security rules:
allow read: if request.auth.token.guild1 == true;
(I'm not sure if/how you can model the guild membership as a map, I'll update this answer if that turns out to be the case.)
Firebase recently announced cross-service security rules that let's you access Firestore data in Firebase storage's security rules. You just need to use firestore. prefix before get() and exist() functions as shown below:
allow write: if firestore.get(/databases/(default)/documents/col/docId/).data.field == "value";
Firebase current supports only 1 database instance per project so the name must be (default) in path. It's not a wildcard as in Firestore rules so not $(database)
Update: As of 2022-09-28, Firebase introduced cross-service Security Rules, so the answer below is outdated. See #Dharmaraj's answer below for an example.
You can retroactively validate and delete the file after it's been uploaded using a cloud function trigger.
Warning: this technique is not bullet proof, as the invalid file will be stored in Cloud Storage temporarily or potentially forever if the Cloud Function trigger fails. My preference is to prevent the upload in the first place, but if the logic to determine permission resides in Firestore and can't be stuffed in custom claims, then this is currently the only way if you're uploading files using Firebase's Client SDKs. If you're building a mission critical system, you should upload the file to a Cloud Function and let the Cloud Function store the file in Cloud Storage instead.
When uploading a file, add some metadata indicating who's doing the upload:
const storageRef = ref(
storage,
`files/${fileName}`,
);
const uploadTask = uploadBytesResumable(storageRef, file, {
customMetadata: {
uploaderId: userId,
},
});
Set storage rule to ensure that the user identity metadata can be trusted:
match /files/{fileName} {
allow create: if request.auth != null &&
request.resource.metadata.uploaderId == request.auth.uid
}
Create a cloud function trigger that retroactively validates and deletes:
export const onFinalize = functions
.storage.object()
.onFinalize(async object => {
// We can trust object.metadata.uploaderId, so check Firestore if user is able to upload file
if (!(await canUploadFile(object.metadata.uploaderId, object.name))) {
await storage.bucket(object.bucket).file(object.name).delete();
throw new Error(
`Permission error: ${object.metadata.uploaderId} not allowed to upload ${object.name}`,
);
}
// Continue
});

Resources