Role based access to FireStore - firebase

I am trying to implement role based access to Firebase Firestore but although I took a look at Googles well done documentation I wasn't able to get it working.
I have set a string "role" in each users document. The document itself has the uid of the user.
My rule to access the users collection looks like this and the write rule must be wrong:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{document} {
allow read: if get(/databases/$(database)/documents/users/$(request.auth.id)/role/$(role)) == "Mitarbeiter"
}
}
}
XCode-console error:
[Firebase/Firestore][I-FST000001] Listen for query at users/EStQNC4cYcaSnKpxs4XnQf96brm2 failed: Missing or insufficient permissions.

The path of your document get() is looking inside a subcollection called "role", but you have none:
/users/$(request.auth.id)/role/$(role)
Also, you have no variable called "role" that we can see here.
Using what you've shown, it looks like the role you need is in the document itself, so there's no need for a get(). Just check it like this:
allow read: if resource.data.role == "Mitarbeiter";

Related

Is it possible to prevent send custom id by rules in firebase

Is there any way to prevent user send custom id for newly created document?
For example
This operation create's new document with id "8pqLuAc6BXCVRF7SB6xT"
db.collection("users").add({foo : 1})
And in my application i always want to have id generate by firebase sdk not by user
because as we know user can hack app and trigger operation like this
db.collection("users").doc('CUSTOM_ID').set({foo : 1})
After this operation id will be "CUSTOM_ID" instead generated by SDK like '8pqLuAc6BXCVRF7SB6xT'
So question is how to write rule to ensure me that id is generated from sdk
Update following your clarification:
It is not possible with Security Rules to avoid a user specifying the ID of a document when it is created.
Even if the code in your front end only uses the add() method, one can write his own code and use the doc() method to write to your DB (as soon as he gets the Firebase configuration object, which is easy).
One possibility would be to write to Firestore via a Cloud Function, but this has some drawbacks see this article.
Original answer:
It is not crystal clear what you mean by a "custom id" but I understand that you probably want a given user to be able to create a document in the users collection only if this document ID is corresponding to his user uid (from the Auth service)
The following Security Rule should the trick:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow create: if request.auth != null && request.auth.uid == userId;
// ...
}
}
}

Set createdBy field in document with current userId (auth.uid)

I know that Firebase has the FieldValue class, which can be used to generate e.g. a server-side timestamp when writing a document (link).
What's the preferred practice for inserting the current user's uid into a document?
Having the client provide that field seems to allow misuse - unless I provide a server rule that checks for (new/updated) documents to match the request.auth.uid, something like:
service cloud.firestore {
match /databases/{database}/documents {
match /broadcasts/{broadcast}/chatMessagesCollection/{message} {
allow write: if request.resource.data.uid == request.auth.uid;
allow read: if true;
}
}
}
I can't find anything on the web for the use-case of having a document be populated with the user writing it -- so what's the best take on this?
What you're doing now with security rules to enforce that the provided UID matches the current user is exactly the right thing to do. There is really nothing better (for this specific use case), and this is a common practice.
I've even written about it in this blog series: https://medium.com/firebase-developers/patterns-for-security-with-firebase-per-user-permissions-for-cloud-firestore-be67ee8edc4a

Firestore - disallow reading multiple documents at once

I've got a Firestore collection.
The IDs of the documents are secrets. You should be able to read only the document whose ID you know.
For the sake of simplicity. I'd like to stick to this approach.
However, by default, one can read an entire collection from Firestore, for example
await firestore.collection("secret_documents").get()
Is it possible to allow reading only one document at once, only when it's referred by its ID?
Yes, that is actually quite easy. To control what documents can be accessed, use Firebase security rules for Firestore.
By default your security rules will be read and write, but those can actually be broken down into more granular operations of get, list, create and update. And what you're trying to do is to allow get, but not a list operation. From the documentation:
service cloud.firestore {
match /databases/{database}/documents {
// A read rule can be divided into get and list rules
match /cities/{city} {
// Applies to single document read requests
allow get: if <condition>;
// Applies to queries and collection read requests
allow list: if <condition>;
}
...
So to allow get for everyone and disallow list calls:
allow get: if true;
allow list: if false;
You'll probably want to elaborate on the allow get rule a bit, because it's more common to restrict it, for example to users that are signed in to your project with Firebase Authentication:
allow get: if request.auth.uid != null;
https://firebase.google.com/docs/firestore/security/rules-query

Do I need to do a get for every field of a document I want to access

I see that to get a field of a document in security rules one must use get. The example below shows getting the 'admin' field of some document in the users collection. If I wanted to get another field, would I have to do another get request or can I just do one get request and get all the fields I need in the document.
Here is the example I'm referring to in the documentation.
https://firebase.google.com/docs/firestore/security/rules-conditions
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if exists(/databases/$(database)/documents/users/$(request.auth.uid))
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
}
}
}
Yes, you would have to write another get(). There are no variables in Firestore security rules, so you can't store the contents of a get() in order to use its data multiple times.
Multiple gets accessing the same document might not incur multiple read charges. The documentation states:
Some document access calls may be cached, and cached calls do not count towards the limits.

Firebase firestore rules, read only when document contain specific value

I was learning through official doc for security rules, but i cant make it work.
in my collections users under document user have some map values, one of them is role: "guest". role values can be "guest" or "superAdmin"
i want access to /users only when role == "superAdmin"
here is what i tried
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if get(/databases/$(database)/documents/users/$(userId)).data.role == "superAdmin";
}
}
}
and got error when i log in as superAdmin
ERROR Error: Missing or insufficient permissions.
i believe i followed docs correctly. and found a similar question in SO where says some bug specific to evaluating nested fields in queries. But i have no nested queries. am i doing anything wrong here?
here is my firestore look
Please help.
Instead of using get(), since you're fetching the document at the location you're reading from, simply address it using resource (which is the prefetched document at that location):
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if resource.data.role == "superAdmin";
}
}
}
Only use get() if you're going to a different collection to fetch data.
I think the part that breaks it is allow read: if get(/databases/$(database)/documents/users/$(userId)). Here you are comparing the owner of the document, not the one requesting it. Try replacing userID with request.auth.uid.

Resources