Firebase Security Rules -- using attribute from User document - firebase

I'm struggling to make my Firebase security rules work.
I only want to allow write access to documents in the collection Nouns to users with the attribute 'admin' == true (boolean) in their user document (stored in collection Users).
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{userId}/{documents=**} {
allow read, write: if request.auth != null
&& request.auth.uid == userId
}
match /Nouns/{documents=**} {
allow read: if request.auth != null
}
match /Nouns/{documents=**} {
allow read, write: if request.auth != null
&& resource.data.admin == true
}
}
}
The rules playground gives the following error when I try a write on a document in the Nouns collection:
Error: simulator.rules line [17], column [13]. Property admin is undefined on object.
Can anyone please let me know what I'm doing wrong?

resource.data will contain data of the document being accessed in Nouns collection. If you want to read data from user's document, use get() instead:
match /Nouns/{documents=**} {
allow read, write: if request.auth != null
&& get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.admin == true
}
Collection names are case-sensitive so make sure you enter it correctly.

Related

Firestore rules : User access to a collection that contain userId

I have 2 collections in my Firestore
users : uid, email
periods : periodId, name, owner_id
I need rules for users access only to it's 'users' collection and another that allow read and write to 'periods' collection only if ownerId uid is equal to authentified user id.
I do that
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write : if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
match /periods/{periodId} {
allow read, write : if request.auth.uid == request.resource.data. owner_id;
}
}
}
But it doesn't work.
:(
You don't share the queries corresponding to these security rules, but we can already identify several problems in your Security rules:
1. For /users/{userId} you have some overlapping between create and write.
The following shall solve this problem:
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
See the doc: "In the case where multiple allow expressions match a request, the access is allowed if any of the conditions is true".
2. For /periods/{periodId} you need to split between read and write.
For read access rights, the resource variable refers to the
requested document, and resource.data is a map of all of the fields
and values stored in the document.
For write access rights the request.resource variable contains the
future state of the document.
(source)
So the following should do the trick (untested):
match /periods/{periodId} {
allow read : if request.auth.uid == resource.data.owner_id;
allow write : if request.auth.uid == request.resource.data.owner_id;
}
I would suggest you watch the following official video on Security Rules. Actually the entire "Get to know Cloud Firestore" video series is a must...

How to manage rights in firebase to allow differents users to read/upate/write?

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

Firebase Security Rules - Undefined Resource Error

I have collection Users which has documents with the same Id as the user.uid. I want to allow logged in users to create documents and only update, delete and read their documents which is specified with the same UID as mentioned.
I tried this but it keeps failing.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{document} {
allow create, : if request.auth != null;
allow update, delete, read: if request.auth != null && request.auth.uid == request.resource.data.UID;
}
}
}
In this code i am trying to compare the uid of the logged user with a document field called UID
Have a look at the documentation, it shows exactly the response to your question.
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 != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
The key point is to use the {userId} wildcard expression to match the ID of the document being read/updated/deleted with the uid of the user (i.e. request.auth.uid);

Firebase Cloud Database Rules - how to retrieve data only where the user id matches a key in the object?

Is there a way using firebase rules to retrieve all the projects that have a specific "userID" value. I tried reading all the rules and came up with this rule but they do not work:
I only want to read the documents which matches auth.uid == userID in database.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow public read access, but only content owners can write
match /debtors/{userID}/{documents=**} {
allow read: if request.auth.uid != null && request.auth.uid == userID;
allow create: if request.auth.uid != null;
allow update, delete: if request.auth.uid != null && request.auth.uid == userID;;
}
}
}
I have the following object in collection:
{firstname: "Aacis"
relation: "friend"
userID: "7ScK2T0T3SMUR1NJxjiCiRzTnm62"}
Fetching stream in flutter with:
stream: Firestore.instance
.collection("debtors")
.where("userID", isEqualTo: user.uid)
.snapshots()
Did you set auth.uid as document id, and object key named userID?
Firestore.instance.collection("debtors").doc("7ScK2T0T3SMUR1NJxjiCiRzTnm62").set({
firstname: "Aacis",
relation: "friend",
userID: "7ScK2T0T3SMUR1NJxjiCiRzTnm62"
})
If you want to allow only onwer to read access to then you should set allow read: if request.auth.uid != null && request.auth.uid == userID;
If you want to read the documents which matches auth.uid == resource.data.userID then you should set allow read: if request.auth.uid != null && request.auth.uid = resource.data.userID;
If you want to allow public to read access then you should set allow read: if true;
And More, A read rule can be broken into get and list.
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>;
}
// A write rule can be divided into create, update, and delete rules
match /cities/{city} {
// Applies to writes to nonexistent documents
allow create: if <condition>;
// Applies to writes to existing documents
allow update: if <condition>;
// Applies to delete operations
allow delete: if <condition>;
}
}
}
See:
https://firebase.google.com/docs/firestore/security/rules-conditions
https://firebase.google.com/docs/firestore/security/rules-structure#granular_operations
https://firebase.google.com/docs/reference/rules/rules.firestore.Resource
Updated
A userID in the following code means the document id is auth.uid, and allow only onwer to read access.
match /debtors/{userID}/{documents=**} {
allow read: if request.auth.uid != null && request.auth.uid == userID;
So you don't set auth.uid as document id, and the stored value named userID in the document match auth.uid, you should use resource.data.userID and request.resource.data.userID.
Please try the following code.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /debtors/{document=**} {
allow read: if request.auth.uid != null && request.auth.uid == resource.data.userID;
allow create: if request.auth.uid != null && request.auth.uid == request.resource.data.userID;
allow update, delete: if request.auth.uid != null && request.auth.uid == resource.data.userID;
}
}
}
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
When writing data, you may want to compare incoming data to existing data. In this case, if your ruleset allows the pending write, the request.resource variable contains the future state of the document. For update operations that only modify a subset of the document fields, the request.resource variable will contain the pending document state after the operation. You can check the field values in request.resource to prevent unwanted or inconsistent data updates:
See:
https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation
https://firebase.google.com/docs/reference/rules/rules.firestore.Resource
https://firebase.google.com/docs/reference/rules/rules.firestore.Request#resource

Firebase security rules syntax with arrays/list property

Within a Firebase Firestore collection with path 'organizations' each document contains a list of string userID's of users who can update or delete that document.
export interface Organization{
name?: string,
owners: string[]
}
I would like to create a Firebase security rule that ensures that only a logged in user with a uid that is in this list can edit or delete the object. Unsure of the appropriate syntax.
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{organization} {
allow read: if true;
allow create: if request.auth != null;
/// What should be the syntax here?
allow update, delete: if request.auth != null && (request.auth.uid in resource.data.owners); // <--------- What should be the syntax for this line?
}
Ok, answering my own question here in case it's useful for anyone else.
It looks like the 'in' syntax above actually works even-though it was a complete guess and I wasn't able to find any documentation for it in the firebase security roles documentation.
Final code:
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{organization} {
allow read: if true;
allow create: if request.auth != null;
allow update, delete: if (request.auth != null) && (request.auth.uid in resource.data.owners);
}

Resources