Suppose there are the following rules in place
service cloud.firestore {
match /databases/{database}/documents {
function roomAccess() {
return resource.data.allow_anonymous == true || (request.auth.uid == resource.data.uid);
}
function writeOnlyOwner() {
return request.auth.uid == resource.data.uid;
}
match /rooms/{room_id} {
allow read: if roomAccess();
allow write: if writeOnlyOwner();
allow create: if request.auth.uid != null;
}
}
}
Also, this is what an document in the collection rooms looks like
Question
Is there any way to query the specific room where the access_key is set to some value?
db.collection('rooms')
.where('access_key', '==', access_key)
.get()
It is important to note that i'm querying as the creator of the room (and the room.uid with match my uid
Nonetheless, it fails on access rights.
And the documentation doesn't seem to describe such behavior.
As far as I understood, the query fails because there might be some rooms, with this access_key, for which roomAccess() would fail.
But I can't seem to find a way around it.
My problem was that I should have been checking for
resource.data.allow_anonymous == true || request.auth.uid != null since I wanted to get the room either if the user is authenticated or it is public.
In this case, all the documents retrieved by access_key query match the security contraints
Related
I've a firestore database and I now need to add a new collection.
Each entry of this collection should contain:
Which userId is the owner(field admin)
Which userId has been allowed to edit this element(field writer)
Which userId has been allowed to only read(field reader).
I'm currently only at the first step, and already strugling:
I was hoping to be able to query my collection( /trips/) and get only the one that I'm allowed to access, but I get an error:
FirebaseError: Missing or insufficient permissions.
Here is my rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
match /trips/{trip} {
allow read, update, delete: if request.auth != null && request.auth.uid == resource.data.admin;
allow create: if request.auth != null;
}
}
}
So my questions:
Is this the correct way of managing resource that must be acceeded by multiple people(meaning, I cannot just have the userId in the path since there are multiple users)
How should I query only the documents list that I'm allowed to see?
Thank you very much for your help
As you will read in the doc, "All match statements should point to documents, not collections".
With
service cloud.firestore {
match /databases/{database}/documents {
match /trips {
// ....
}
}
}
you don't point to a document. You should use a wildcard to point to any document in the specified path, as follows:
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
// ....
}
}
}
Therefore the following should correctly implement your requirements:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /trips/{trip} {
allow read: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
|| request.auth.uid == resource.data.reader
);
allow update: if request.auth != null &&
(request.auth.uid == resource.data.admin
|| request.auth.uid == resource.data.writer
);
allow create: if request.auth != null;
}
}
}
Then, for the two questions:
Is this the correct way of managing resource that must be acceeded by multiple people (meaning, I cannot just have the userId in the path
since there are multiple users)
If the admin, writer and reader are specific for each document, yes this is the correct way. If those roles would be more global (e.g. all the trips to Europe can be edited by the same user), you could use a role based approach with Custom Claims.
How should I query only the documents list that I'm allowed to see?
It is important to note that rules are not filter. So your query for getting docs needs to be aligned with the rules. In your specific case, you could have an additional field of type Array which contains three values; the uids of the admin, writer and reader, and use the array-contains operator. Something like:
const user = firebase.auth().currentUser;
const query = db.collection("trips").where("authorizedReaders", "array-contains", user.uid);
match /{document=**} {
allow read, write: if false;
}
You don't need the above code as it will apply to all routes of the database, because of the above line you are getting the below error as it does not allow you to read and write to the database
FirebaseError: Missing or insufficient permissions.
Now, if you want to assign privileges to users then you should add the Role field to users collections which would have a value such as Admin, Editor, Reader
Then, you can check in routes something like below
match /users/{userId}/trips/{tripId} {
allow read, delete: if request.resource.data.role == "Admin";
allow create, update: if request.resource.data.role == "Admin || request.resource.data.role == "Editor";
}
If you want to know more about how to create a route check out this video for the best explanation
I finished making my app and I'm implementing the security rules.
I am currently trying to validate whether or not a user exists in the bank, if he exists, he will be able to read and write to a specific path. I'm using Firestore:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents{
match /Usuarios/{documentID} {
allow read, update, delete: if request.auth.uid != null && request.auth.uid == documentID;
allow create: if request.auth.uid != null;
match /Manejo/{manejoID} {
allow write, read: if exists(/databases/$(database)/documents/$(request.auth.uid)); // The id is never exist... even when it does exist in my database, this operation simply DOES NOT WORK despite the document with the Uid existing in the database
}
}
}
Edit1:
match /Manejo/{manejoID} {
allow write, read: if true;
}
This condition not work too...
You need to declare in which collection you want to check the existence of the doc with docId == userId
With
allow write, read: if exists(/databases/$(database)/documents/$(request.auth.uid));
you don't declare the collection.
Look at the following example in the doc, which checks for the existence of the doc with docId == userId in the users collection. See how /users/ is added right after $(database)/documents.
allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid))
I understand that want to check the existence of the doc in the Usuarios collection. If this assumption is correct, do as follows:
allow write, read: if exists(/databases/$(database)/documents/Usuarios/$(request.auth.uid));
Note that you may be interested by this Firebase blog post.
In my app I want to be able to have books assigned to a reader without there being any duplicates. Can I assign a rule like that to firestore or is that something to handle in my swiftui code? The security rules so far is as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match/users/{uid}{
allow read: if request.auth.uid == uid
allow create
}
match/books/{id}{//display library if user logged in
allow read: if request.auth != null
}
match/reader/{uid}{//check for readers assigned to user
allow read: if request.auth != null && request.resource.data.uid == request.auth.uid;
allow write: if request.auth != null;
match/reader_books/{id}{
allow read: if request.auth != null
allow write:if request.auth != null
}
}
}
}
EDIT: I tried to create a function in swiftui code to check for duplicates here:
func checkData(_ readerBooks: ReaderBooks){
print("reader ID : \(readerBooks.readerID)")
let checkBooks = db.collection("reader_books").document("\(readerBooks.readerID)")
checkBooks.getDocument{(document, error) in
if let document = document, document.exists{
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
}else{
print("Document does not exist")
self.postData(readerBooks)
}
}
}
When trying to add a book to a reader it always returns document does not exist in the console even when the readerID is present in the document's field. Is there an issue with the code somewhere?
You don't need security rules for this. In the reader_books subcollection, just use the same ID as the book from the books collection. Since a collection can't have two documents with the same ID, it will be impossible for one book to appear in that collection twice.
This documentation page: Writing conditions for Cloud Firestore Security Rules, says the following:
Another common pattern is to make sure users can only read and write their own data
And provides this example:
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 read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
I don't understand why the create rule is not defined with the same condition as the rest, if request.auth.uid == userId, but instead is defined with if request.auth.uid != null. As I understand it, with this rule any user can create any document inside users, but cannot do anything with it unless it matches his uid. So why allow it at all?
Let's talk about the very basic security rule that could be implemented (with user authentication):
allow read, update, delete: if request.auth.uid != null;
allow create: if request.auth.uid != null;
where any user can delete the documents of another people creation. So to restrict/control it, we implement as shown in the code snippet provided.
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 read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
The code snippet is just an example use case that uses different conditions for reference purposes as this is a tutorial/guide, so the Firebase team try to fit as many possible conditions to the code snippet.
You can, of course, do allow create: if request.auth.uid == userId; to strictly restricted to that particular user.
I hope it gives you some idea!
I've made an app where users can post records to the firestore database. I'm now at the point where I'm implementing security rules but I'm struggling to find a solution.
My code looks like this
docRef.get().then(function(doc){
if(doc.exists){
docRef.set(//data to set here)
} else {
docRef.update(//data to update here)
}
.catch((error) => {
alert('Error' + error);
})
My rules are currently set to allow the user to create an object if they are authorised, and only allow an update if the user id on the record matches themselves.
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user}{
allow create: if request.auth.uid != null;
allow read, write: if request.auth.uid == resource.id;
}
match /equipment/{document} {
allow create: if request.auth.uid != null;
allow read, update: if request.auth.uid == resource.data.user;
}
I think the problem lies in that I'm trying to get the object prior to creating it. However I need to perform these checks prior to writing to the document.
Am I right in thinking this the problem? and if so is there a solution I can implement.
Thanks
I'm still working on my rules, but I hope this helps.
If you're trying to make sure the user is editing something they own and their uid is the same as the document id....
function isOwner(userId) {
return request.auth.uid == userId
}
match /users/{userId} { //this is the document
allow write: if isOwner(userId);
}
If you're trying to make sure they are the creator of a document:
match /equipment/{documentId} {
allow create: if request.auth.uid != null;
allow read, update: if get(/databases/$(database)/documents/equipment/documentId).data.userId == request.auth.uid;
}
https://firebase.google.com/docs/firestore/security/rules-conditions?authuser=0
You need to use the get function to retrieve a document you're interested in. That function returns data that has the related fields you can compare to, in this case, whatever field name you stored the user ID on so you can compare it to their auth.uid.
I think you should change the 'create' rule as following:
allow create: if request.auth.uid != null &&
request.auth.uid == request.resource.data.id;
This is what documentation says: "if your ruleset allows the pending write, the request.resource variable contains the future state of the document."