We store user permissions in claims.
Here's how the claim of a enterprise customer looks like:
{"roles": ["enterprise"]}
Then, in the rules of Firebase Storage, we try to check whether a customer is enterprise before they are granted access to some files:
function isEnterprise() {
return (request.auth.token.roles) && ("enterprise" in request.auth.token.roles);
}
Then, when the user attempts to retrieve the Download URL of a file from the web using getDownloadURL, Firebase throw a permissions error.
Could you please provide a solution?
Fixed it with this:
function isEnterprise() {
return request.auth.token.roles.hasAny(['enterprise']);
}
Related
This question already has an answer here:
Creating Firebase Storage Security Rules Based on Firebase Database Conditions [duplicate]
(1 answer)
Closed 1 year ago.
I want to allow read permission on a file in Firebase Storage only if the value of a certain node in the Firebase Realtime Database is true. Is it possible to do so? If yes, then how?
That is not possible at this time. You would have to use Cloud functions or your own servers using the Admin SDK to do that.
The Admin SDK essentially has full access to your Firebase project's resources so you can check if the value you are looking for in the Admin SDK exists. If yes, proceed with getting the signed URLs (or the data you are looking for from storage) else return an error.
A simple cloud function for that would be something like:
exports.getStorageData = functions.https.onCall((data, context) => {
const {uid} = context.auth;
const {fileName} = data;
if (!uid) return {error: "User not authorized!"}
// Read for data in realtime database
const dbRef = admin.database().ref("/path/to/data");
if ((await dbRef.once("value")).val()) {
return {error: "Required Value is not true"}
}
//Get signed URL or any data from storage
const storageRef = admin.storage().bucket()
//.....
return {data: "..."}
});
You need to use such secure env such as server or functions as only client side validation is not secure. But is the true value in your database something like a role or anything? You can try adding those in Custom Claims. I'm not totally sure about your use case but if it something like allowing access to specific folders or something then you can add a claim the_folder_id: true. This is not the best solution if a user can have access to a lot of folders. In that case you can assign groups as mentioned in the documentation. But satisfies your needs then you can try the following security rules along with this.
// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
allow read: if resource.metadata.owner == request.auth.token.groupId;
allow write: if request.auth.token.groupId == groupId;
}
I'm trying to trigger my function on specific path in storage bucket using:
exports.generateThumbnail = functions.storage.bucket("users").object().onChange(event => {});
When I try to deploy it, console shows:
functions[generateThumbnail]: Deploy Error: Insufficient permissions to (re)configure a trigger (permission denied for bucket users). Please, give owner permissions to the editor role of the bucket and try again.
How can I do that? Do I nedd to setup IAM or bucket permission or maybe something else?
Looks like the issue is that you're trying to reference a bucket named "users" rather than filtering on an object prefix.
What you want is:
exports.generateThumbnail = functions.storage.object().onChange(event => {
if (object.name.match(/users\//)) {
// do whatever you want in the filtered expression!
}
});
Eventually we want to make prefix filtering available so you can do object("users"), but currently you have to filter in your function like above.
I'm sure I'm missing something wrt Firebase Storage rules, but I've done the following:
STEP 1
Firstly I set the following Firebase Storage rule:
service firebase.storage {
match /b/{bucket}/o {
match /items/{dev_key}/{perm_id}/{file_name} {
allow write: if request.auth.uid == dev_id;
allow read: if request.auth.token.permId == perm_id;
}
}
}
I expected only signed in users with a custom claim permId matching the relevant location to be able to download the file, allow read: if request.auth.token.permId == perm_id;.
So, I then set a custom claim in Cloud Functions on a user as follows:
STEP 2
admin.auth().setCustomUserClaims(uid, {permId: '1'}).then(() => {
// send off some triggers to let user know the download is coming
admin.database().ref(`collection/${uid}/${itemId}`).update({
downloadReady: true
});
});
Then I signed the user out and signed back in again... which set the custom claims.
I checked that they were set in Cloud Functions as follows:
STEP 3
admin.auth().verifyIdToken(idToken).then((claims) => {
console.log("--------------claims -------------");
console.log(JSON.stringify(claims));
});
And I saw in the claims string... permID: "1"
On the client side I then requested a downloadURL (here is hopefully where I'm going wrong)... I expected this to not be the public download url but rather the download url that the Firebase Storage security rules will check:
STEP 4
var pathReference = storage.ref('items/<some-key>/1/Item-1');
pathReference.getDownloadURL()
.then((url)=>{
console.log("url: ", url);
})
The url I received from this call gave me this link
https://firebasestorage.googleapis.com/v0/b/emiru84-games.appspot.com/o/games%2FcfaoVuEdJqOWDi9oeaLLphXl0E82%2F1%2FGame-1?alt=media&token=45653143-924a-4a7e-b51d-00774d8986a0
(a tiny little image I use for testing)
So far so good, the user with the correct claim was able to view this image
I then repeated step 2, logout/login again, except this time with a permId of "0". I expected the url generated previously to no longer work since my user no longer had the correct custom claim... and the bucket location was still at the same location (bucket/dev_key/1/filename) but it still worked.
If I repeated step 4 I got a new url, which then gave the appropriate 403 error response. However the old url still worked (I guess as long as the token parameter is tacked on). Is this expected, if so, I'm not sure I understand how the Storage security rules make a difference if the download url is public anyway?
Any help clearing my foggy brain would be appreciated.
The download URL in Cloud Storage for Firebase is always publicly readable. It is not affected by security rules.
If you don't want to allow public access to a file, you can revoke its download URL.
I've recently built an app using Firebase as the data store and secured it using the security rules that only the user can read and edit their data which all works fine.
But now I want to build an admin section to list users and update details if need be, but the problem I'm running into is the fact that I cant access their data as I'm not the user. I'm seeing if its possible to allow read or write permissions to the user or admin?
UPDATE
Token generation
var tokenGenerator = new FirebaseTokenGenerator(authSecret);
var token = tokenGenerator.createToken({admin: true});
Security rule
".read": "auth.admin == true || otherauthmthod"
The method that you described above will work, assuming you update your security rules to look for the auth.admin bit. Alternatively, and likely a bit easier, is to generate an admin token, which will allow you to skip the execution of security rules entirely. This can be accomplished via:
var token = tokenGenerator.createToken({ any: "data" }, { admin: true });
See https://github.com/firebase/firebase-token-generator-node for more details.
How could you setup rules on Firebase which would allow a user to become a paid user of your app? For example, if I have the following data structure:
{
users: [
{
isPaid: false
},
{
isPaid: true
}
]
}
How could you setup firebase rules to not allow the user to update it themselves (by fudging a request), but still allow it to be updated automatically when they "pay" for your app?
I've thought about randomly generating a number and asking the user to enter that number or something like that, but I don't think that would work... Has anyone done something like this?
You'll need to have a server process that securely writes the paid flag using a Firebase secret (that can be found on Forge for your Firebase). Set the ".write" rule for /users/isPaid as false - the server code can bypass this rule since it knows the secret. You should call firebaseRef.auth(secret) from your server code first.