Firestore rules on subcollection - firebase

I have a "game" collection on firestore with a "levels" sub-collection. I'm trying to set-up the security rules so that you can only access game or level you created.
All documents (games or levels) have an authorId field with the uid of the user that created them. I have try this rule but still got an Missing or insufficient permissions error:
service cloud.firestore {
match /databases/{database}/documents {
match /games/{document=**} {
allow read, write: if document.data.authorId == request.auth.uid;
}
}
}
What am I missing?
I have tried the following rules too with no success:
service cloud.firestore {
match /databases/{database}/documents {
match /games/{game}/levels/{level} {
allow read, write: if level.data.authorId == request.auth.uid;
}
}
}
service cloud.firestore {
match /games/{game} {
allow read, write: if game.data.authorId == request.auth.uid;
match /levels/{level} {
allow read, write: if level.data.authorId == request.auth.uid;
}
}
}

According to the reference documentation, resource is the object that contains the document data that the user is trying to write. You use its data property to get a hold of its field values.
service cloud.firestore {
match /databases/{database}/documents {
match /games/{document=**} {
allow read, write: if resource.data.authorId == request.auth.uid;
}
}
}

match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth.uid == uid;
}
match /data/{uid}/temperature
/{document=**}{
allow read, write: if request.auth.uid == uid;
}
match /data/{uid}/blood_pressure
/{document=**}{
allow read, write: if request.auth.uid == uid;
}
}
}
I did this to access subcollections "blood_pressure" and "temperature" for only authenticated users. It works fine for me.

Related

In Firestore rules how do you check the id of the requesting document?

I mean I want to check to be sure the document id of the request is the same as the document id in the database.
I tried this but it doesn't work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.resource.id == userId;
}
}
}
Also tried this and it also didn't work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.resource.id == resource.id;
}
}
}
I've searched online for hours and every single post is about using authentication uid.
if you want to make sure the uid in request matches name of the user document, then use this:
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
}
and if you want to allow new users to be created add this too:
allow create: if request.auth != null;
more details : https://firebase.google.com/docs/firestore/security/rules-conditions

Firestore security rules and vuefire

i have the following sample app here: Github repo
It uses vuefire in ChatList.vue
// vuefire firestore component manages the real-time stream to that reactive data property.
firestore() {
return {
chats: db.collection('chats').where('members', 'array-contains', this.uid)
}
},
I now wrote security rules to secure the data, but can't seem to get the combination of vuefire and security rules to work:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid != null;
}
// THIS WORKS AS INTENDED, AND I'D LIKE TO INCLUDE "READ"
match /chats/{chatId}/{documents=**} {
allow write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
So the goal is: make the individual chats only readable and writable to users that are in the members array in firestore. (Currently i achieved this partially, since all chats are readable to anyone, but only writable to users in the members array.)
Do i have to rewrite the vuefire component so i can have the following security rule? (It gives an error message: listing of chats not possible due to missing permissions)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
For completeness, the working solution is (credits to Renaud Tarnec):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
match /chats/{chatId}/{documents=**} {
allow read, write: if chatRoomPermission(chatId)
}
function chatRoomPermission(chatId) {
return request.auth.uid in get(/databases/$(database)/documents/chats/$(chatId)).data.members;
}
}
}
Since you want to check, in your Security Rules, if a given value (the user uid in this case) is contained in a field of type Array in your document, you can use the in operator of the List type.
So, the following should do the trick:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
// THIS IS THE PART I'D LIKE TO REMOVE
match /chats/{chatId=**} {
allow read: if request.auth.uid in resource.data.members;
}
// ....
}
}

Firestore Rules

I have the following rules in my Firestore database. But I still keep getting a notification from Firestore that the rules I set in my database are not secure. Please see the codes below. Any suggestions or recommendations to make the database more secure?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if userIsAdmin();
}
match /Basket/{Basket} {
allow read, update, delete: if userOwnPost();
allow create: if request.auth.uid != null;
}
match /AllOrders/{AllOrders} {
allow read, create, update: if userOwnPost();
}
match /Items/{Items} {
allow update: if userOwnPost();
}
match /Voucher/{Voucher} {
allow update: if userOwnPost();
}
match /User/{User} {
allow read, update: if userOwnPost();
allow create: if request.auth.uid != null;
}
function userIsAdmin() {
return getUserData().userRole == 'Admin';
}
function getUserData() {
return get(/databases/$(database)/documents/User/$(request.auth.uid)).data;
}
function userOwnPost() {
return getUserData().objectId == request.auth.uid;
}
}
}
You have some overlapping match statements in your rules:
With
match /{document=**} {
allow read: if true;
allow write: if userIsAdmin();
}
you allow read access on all documents in your Firestore database.
As explained in the doc (section "Overlapping match statements"), "in the case where multiple allow expressions match a request, the access is allowed if any of the conditions is true".
So all your other security rules are just overlapped by this one.

firebase firestore rules authenticated access to all collections except one

I have the following firestore structure, basically 3 collections
publicdata
protecteddata1
protecteddata2
I want to have protecteddata1 and protecteddata 2, and really the entire firestore database as authenticated users only.
But i want the public to have readonly access to 'publicdata' collection..
The following is my attempt but it doesn't work
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if (request.auth.uid != null);
}
match /publicdata {
allow read;
}
}
}
You can use the following functions I created to do this
function isUserAuthenticated() {
return request.auth.uid != null;
}
You can then use it like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isUserAuthenticated();
}
match /publicdata/{itemId} {
allow read : if true;
allow create : if isUserAuthenticated();
allow update: if isUserAuthenticated();
allow delete: if isUserAuthenticated();
}
/* Functions */
function isUserAuthenticated() {
return request.auth.uid != null;
}
}
}
As others have explained, if you have multiple matches for the same document they are OR'ed together, so you can't implement an exception with that.
What you can do though is capture the collection name in a variable, and then implement the exception in a single match:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{collection}/{document=**} {
allow read: if collection != 'publicdata';
allow write: if (request.auth.uid != null);
}
}
}
So here we allow reads from all collections except publicdata.
Because here is says:
Overlapping match statements
It's possible for a document to match more than one match statement. In the case where multiple allow
expressions match a request, the access is allowed if any of the
conditions is true: ...
You can use this:
rules_version = '2';
service cloud.firestore {
// Check if the request is authenticated
function isAuthenticated() {
return request.auth != null;
}
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if isAuthenticated();
}
match /publicdata/{document=**} {
allow read: if true;
}
}
}

Access parent documents field in Firestore rules

I'm implementing a recipe book in Firestore where every user is able to see all the recipes all users created but only the original author of the recipe is allowed to edit or delete the recipe. Any user is also allowed to create a new recipe.
My problem is that I am unable to setup the permissions a subcollection to "listen" on a field of the subcollections parentdocument.
Each recipe document contains three things. A field called name where the name of the recipe is stored, a field called creatorUID where the request.auth.uid of the creators uid is stored and a subcollection called ingredients containing documents with some random fields.
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
match /{list=**} {
allow read: if isSignedIn();
// Should return true if recipe.creatorUID has same value as request.auth.uid
allow write: if recipe.creatorUID == request.auth.uid;
}
}
}
}
The problem is that with these rules it only works to create the recipe document. The subcollection and it's documents are not created since the db says
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
FirebaseError: Missing or insufficient permissions.
The calls is made from Angular client and it's official library.
Rules don't cascade, so you'll need to perform whatever checks you need for the document being captured by the Rules.
Generally speaking, {x=**} rules are more often a mistake and the usage of =** only for extremely specific use cases.
From your question, I'm assuming your data mode is something like this:
/ListofRecipes/{recipe_document}/List/{list_document}
In this case, you'll need your Rules to be configured something like this:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
function recipeData() {
return get(/databases/$(database)/documents/ListOfRecipes/$(recipe)).data
}
match /List/{list} {
allow read: if isSignedIn();
allow write: if recipeData().creatorUID == request.auth.uid;
}
}
}
}
Dan's answer above works great! Just for reference, in my case I only needed the root parent document ID, you can use the variable from the match statement above the nested one, like this:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipeID} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
match /List/{list} {
allow read: if isSignedIn();
allow write: if recipeID == 'XXXXX';
}
}
}
}
Building upon Dan's answer, you should be able to reduce the number of reads on your database for update and delete on the subcollection by adding the creatorUID to the subcollection document.
You'll have to restrict create to just the creator and make sure the creatorUID is set. Here's my modification of Dan's rules:
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
match /ListOfRecipes/{recipe} {
allow read, create: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
function recipeData() {
return get(/databases/$(database)/documents/ListOfRecipes/$(recipe)).data
}
match /List/{list} {
allow read: if isSignedIn();
allow update, delete: if resource.data.creatorUID == request.auth.uid;
allow create: if recipeData().creatorUID == request.auth.uid
&& request.resource.data.creatorUID == request.auth.uid;
}
}
}
}

Resources