I'm trying to secure my Firebase (google cloud storage) files based on user data. In firestore, I use a rule based on getting database content (look up the uid in users table and match a field) and that's working fine. I'm trying to use the same kind of rules in firebase storage but in the simulator I get Error: simulator.rules line [12], column [17]. Function not found error: Name: [get].; Error: Invalid argument provided to call. Function: [get], Argument: ["||invalid_argument||"]. My rules look like this:
match /b/{bucket}/o {
function isAuth() {
return request.auth != null && request.auth.uid != null
}
function isAdmin() {
return isAuth() &&
"admin" in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
}
function clientMatch(client) { // expects user's "client" field to be ID of client
return isAuth() &&
client == get(/databases/$(database)/documents/users/$(request.auth.uid)).data.client;
}
match /P/Clients/{client}/{allPaths=**} {
allow read, write: if isAdmin() || clientMatch(client);
}
}
}
Line 12 is the one beginning client == get, in clientMatch().
I haven't been able to tell whether these functions are only supported for Firestore (db) rules, or whether they should work for storage as well.
If this doesn't work, what are my options? How do people look up user data for Firebase storage security?
You currently can't reference Firestore documents in Storage rules. If you would like to see that as a feature of Storage rules, please file a feature request.
Consider instead using a Cloud Functions storage trigger to perform some additional checks after the file is uploaded, and remove the file if you find that it's not valid.
This is now possible with cross-service security rules. You have to use firestore. namespace before the get() and exists() functions as shown below:
function isAdmin() {
return isAuth() && "admin" in firestore.get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
}
Do note that you'll be charged for read operations just like in Firestore security rules.
Related
This is a follow up on this post: Firebase Firestore security rules - read all nested docs based on resource (but it's null)
I'm having a hard time figuring how Firestore works!
So let's say I have this Bases collections:
{
name: "Base 1",
roles : {
user_1: true
}
},
{
name: "Base 2",
roles : {
user_2: true
}
}
I want to fetch all Base where user_1 is in resource.roles. I do :
db.collection('bases').where('roles.user_1', '==', true).get()
And it works great. All server side so no one can try to alter the filter: All good.
But, I want to enforce this filter with a security rule in firestore. Just to be sure! So I've wrote:
function hasRole(){
return request.auth.uid in resource.data.roles
}
match /bases/{document=**}{
allow read: if true; // signedIn() && hasRole();
}
match /bases/{baseId}{
// allow read: if signedIn() && hasRole();
allow update, delete: if false && signedIn()
allow create: if signedIn()
}
And it works when I try to access a specific resource in the rule simulator but now my .get() method receive a not-enough permission. Funny enough, When I uncomment the read into /bases/{baseId} it works perfectly (if I comment the /bases/{document=**} of course).
So I can make it work but I'd still like to understand why the {document=**} rule work in simulator and not when I'm trying to fetch from a server.
Thanks a lot for your help :)
P.S: I didn't find a way to simulate a nodejs .get() in the simulator which is not super convenient to learn this stuff. Any resource on that will be read and helpful.
In a firestore database, I am using email only as authentication. The database has the following structure:
companies
jobs
users
For sake of readability (and for customer's peace of mind), I am using the email address as the Document ID for the users collection. A user document looks like this:
document id: tbogard#gmail.com
fields
name_first: Terry
name_last: Bogard
jobs_read: ["job_A"]
jobs_readwrite: ["job_B, job_C"]
When I try to grab the request token in the rules, it gives me errors (search for ***):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function userExists(){
// *** Function not found error: Name: [exists]. ***
return exists(/databases/$(database)/documents/users/$(request.auth.token.email));
}
function userData(){
// *** Function [get] called with malformed path: /databases/(default)/documents/users/ ***
return get(/databases/$(database)/documents/users/$(request.auth.token.email)).data;
}
// For now, let's keep it simple, and enforce the User read/readwrite rules on Jobs.
match /jobs/{jobId}{
allow read: if userExists() && (jobId in userData().jobs_read || jobId in userData().jobs_readwrite);
allow write: if userExists() && jobId in userData().jobs_readwrite;
}
// Only allow Users to see their own profile and companies they belong to.
match /companies/{companyId}{
allow read: if userExists() && userData().email in resource.data.employees;
allow write: if false;
}
match /users/{userId}{
allow read: if userExists() && userData().email == resource.data.email;
allow write: if false;
}
}
}
I'm guessing request.auth.token.email is returning something like an optional? I can't find anything in the documentation explaining how the functions get/exists, which require a path, handle this. Is there a way that I could make the Firestore UID for each user the email address instead of the random string, or can I fix these rules some way?
I am having difficulty trying to diagnose a particular rule in my firestore.rules file. See that question here for context.
Is there a way to debug the firestore.rules file and/or functions? I'm using unit testing and the emulators to test my rules, but I would really love to see exactly what values are being evaluated by the rules engine.
For instance, here is my firestore.rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin();
}
}
}
}
}
function isAdmin() {
// removed for security
}
function belongsToCurrentClass() {
// retuns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
What I'd love to do is set breakpoints or step through the code. When attempting CRUD operations on a organizations/{orgId}/classes/{classId}/students/{studentId} path I'd love to inspect exactly what values the orgId, classId, and studentId variables are holding, as well as the resource and request parameters. I'd love to inspect exactly which document (if any) is returned by the get request in belongsToCurrentClass and what the return value is.
Does anyone know of any way to do this? I think I'd answer my question referred to above in 10 seconds if I could just see the data being evaluated.
There is a local emulator for Cloud Firestore security rules. This is your best (and really only) tool for digging into security rule execution. There is no step-through debugging, but you can see a lot of debug output in the console.
https://firebase.google.com/docs/rules/emulator-setup
We can add the built-in debug function to rules. As noted in a comment, you'll see an unhelpful message like this in the browser:
Received: [path] Expected: [bool]. for 'list' # L6
On the plus side, we won't forget to remove debug messages. Tail the log file to see the output: tail -f firestore-debug.log
For example, to see which paths are being called:
match /databases/{database}/documents {
match /{document=**} {
allow create, read, update, delete: if debug(request.path);
}
}
I have a set of invites and I'm getting an insufficient permissions error.
The Data
"inviteID" : {
playerEmail: 'jon#doe.com',
teamID: 'abcdefg'
}
and a team
"teamID" : {
...
managers: [managerID, ...]
...
}
with the following queries:
Team Manager
Query seems straight forward
db.collections('invites').where('teamID', '==', myTeamID)
but the rules have to check if the person querying is the manager ...
match /invites/{inviteID} {
function isManager() {
// managers is an array
return get(/databases/$(database)/documents/teams/$(resource.data.teamID)).data.managers.includes(request.auth.uid)
}
allow read: if (isManager())
}
Does it matter that I also have this rule that will fail?
// Allow user to read his own invites
match /invites/{inviteID} {
allow read: if (request.auth.token.email == resource.data.playerEmail)
}
The second rule that matches the same document will fail because obviously the invites will not have the same email as the manager. I need this rule as a separate query allows users to query their invites.
Seems to not read, I'm having a permissions issue. Appreciate the help!
So i changed the rule to
return request.auth.uid in get(/databases/$(database)/documents/teams/$(resource.data.teamID)).data.managers
and this seems to solve my permissions error
I just installed FriendlyPix iOS and followed all the steps listed on the readme including deploying the cloud functions and rules. I am getting an error and I think it has to do with the storage rules. The error I get is:
User does not have permission to access gs://friendlypixsk.appspot.com/HS4uNP1BYNNnW2Qf59azZH8i3gp2/full/-LAoHLCCMnlD8RoFhUNd/jpeg.
This happens in the uploadPressed function in FPUploadViewController. It happens in the fullref.downloadURL function call. The closure passed to that function is getting an error.
The Firebase Storage rules I have are as follows:
// Returns true if the given UID matches the signed in UID,
// the uploaded file is an image and its size is below the given number of MB.
// Also allow deletes.
function isImageAndBelowMaxSize(uid, maxSizeMB) {
return request.auth.token.admin == true || (request.auth.uid == uid
&& (request.resource == null // Allow deletes
|| request.resource.size < maxSizeMB * 1024 * 1024 // Max size for the uploaded file
&& request.resource.contentType.matches('image/.*'))) // The file is an image
}
service firebase.storage {
match /b/{bucket}/o {
match /{userId}/thumb/{postId}/{fileName} {
allow read, write: if isImageAndBelowMaxSize(userId, 1);
}
match /{userId}/full/{postId}/{fileName} {
allow read, write: if isImageAndBelowMaxSize(userId, 5);
}
}
}
I have not modified them at all. The image does get uploaded and I can see it in the firebase console. I'm a little new to firebase and not sure exactly how these rules work. I have signed in using google sign in.
Any help would be greatly appreciated!!
Thanks