Firebase rules for collection group subcollection: resource.data is empty? - firebase

I use collection grouped subcollections, so (in my understanding) I have to use a wildcard:
match /{path=**}/actions/{action} {
allow read, write: if request.auth != null;
}
This is working so far.
It'sworking also when implenting another rule:
match /{path=**}/actions/{action} {
allow read, write: if request.auth != null
&& request.auth.token.email !='';
}
But I want to check if the user has access with data in the document and there comes the problem:
match /{path=**}/actions/{action} {
allow read, write: if request.auth != null
&& request.auth.token.email in resource.data.access;
}
leads to
FirebaseError: Missing or insufficient permissions.
with:
this.afs.collectionGroup("actions", ref =>
ref.where("owner.email", "==", user.email)
).valueChanges({ idField: 'id' }).pipe(take(1))
So far I couldn't find any further information about how to access data within a wildcard.

The data of document being accessed is present in resource and not request. So the rule should be:
request.auth.token.email in resource.data.access;
Do note that here resource has data of the document in actions sub-collection (/actions/{doc}).
You can find more information about data validation in the documentation.

Related

Firestore rules not working like they should. Path and values are correct, but still saying invalid permissions on read

So I have these firestore rules here:
match /Customers/{customerId} {
allow read, update, create, delete: if request.auth != null && get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.businessId in resource.data.businessIds || request.auth != null && request.auth.uid == resource.data.uid || request.auth != null && get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.businessId == resource.data.businessId || request.auth != null && get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.customerId == resource.data.customerId;
match /ServiceLocations/{ServiceLocationId} {
allow read, update, create, delete: request.auth != null && get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.businessId == resource.data.businessId;
}
}
And for some reason when I am calling read on /ServiceLocations/{ServiceLocationId} inside the /Customers/{customerId} hierarchy I am getting invalid permissions. But I am confused because the path and values are correct. I am able to read the customerId document fine so there is no issue there, but specifically I cant read the serviceLocationId documents which is a document inside the ServiceLocations subcollection inside the customerId document.
get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.businessId
is a valid path which returns a businessId.
resource.data.businessId
and businessId is a valid field inside the serviceLocationId document and both values do infact equal eachother. Yet it is still returning false.
Attached are two images showing the "Users" document of the user I am sending the request from which includes the businessId field, and the serviceLocationId document I am trying to read from, which also includes the businessId field. And as you can see both values do infact match and the paths are correct.
https://i.stack.imgur.com/vJNPN.png
https://i.stack.imgur.com/W8sgM.png
Here is an example of one of the requests being called invalid:
db.collection("Customers")
.doc(selectedCustomerData.customerId)
.collection("ServiceLocations")
.onSnapshot((snapshot) => {
Am I just doing something wrong? It makes no sense to me. Any help would be appreciated.
I suspect this is a case where the security rules cannot filter your data for you.
Specifically, I think the rules may not be able to enforce this requirement on their own:
get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.businessId ==
resource.data.businessId;
Try passing a filter in the query where you also pass the user's business ID (.where('businessId', '==', 'The business ID of the user')), or pass the business ID as a custom claim rather then looking it up in a document.

How to have different rule for a specific collection in firebase firestore

I have a firestore db, for all collections I want to have below rule
allow read : if request.auth.uid != null ;
allow write: if request.auth.uid != null ;
except for service-account collection, which I don't want anybody has access (only firebase functions since they are running under admin service account) so I updated my rules to
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read : if request.auth.uid != null ;
allow write: if request.auth.uid != null ;
}
match /service-account/{serviceAccount} {
allow read: if false;
allow write: if false;
}
}
}
Then I tried to test it via Rules Play Ground, rules are teken into account but result is not correct
So my rule correctly ban access, but the first rule which is going to be applied for all, seems to allow read. I changed the order and no diffrenece.
How can I fix this.
The problem is that this rule is unconditionally applied to every single document in your database:
match /{document=**} {
allow read : if request.auth.uid != null ;
allow write: if request.auth.uid != null ;
}
With this in place, all documents are readable and writable by all users. You cannot override this with another rule. Once a document is deemed readable by any rule, that can't be changed.
See the documentation for overlapping match statements.
What you will need to do instead is call out each individual collection by its name (except service-account), and apply the permissions to them individually.

Limiting permissions to the author not working in Firestore rules

I am attempting to move my Firebase rules from testing where every user could read and write every document to one where only the author can update or delete documents they create.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth != null;
allow update, delete: if request.auth != null && request.auth.uid == resource.data.author_uid;
allow create: if request.auth != null;
}
}
}
This set of rules is resulting in a 'Missing or insufficient permissions" error at Firebase init and if I attempt to delete a document. If I go with my original rules then everything works.
allow read, create: if request.auth != null
I do a standard firebase.initializeApp (but dont want to publish my keys here - lets just say the same initialize works with the basic rules and on three other firebase projects I have). The delete call is as follows and works with the simpler rule set as well but not the tighter rules above:
const decrement = firebase.firestore.FieldValue.increment(-1);
firestore.collection('story').doc(storyid).delete().then(function() {
firestore.collection('users').doc(getUserID()).update({
saves: decrement
});
})
(thank to Sam Stern on the FB team for guidance)
First, there was a mistake in the rules description. While request.auth.uid is defined by firebase the resource.data.author_id needs to be defined by the developer on each of their documents. In this case the 'story' document contains a 'creator' property that is the userid of the owner. So the correct variable would be resource.data.creator in the above rules.
In addition its possible to define the documentid as the userid, as is often the case when you are creating a 'user' object for each account in your firebase app. In this case the userId is the same as the documentId so the following rule would control update permissions that only allow the owner of that document to change it.
match /users/{userId} {
// In this scope the "userId" variable is equal to the documentId because of the wildcard declaration {userId}
allow update: if request.auth.uid == userId;
}

Rule for Firestore that enforce user to write a document with id equals to its own uid

I would like to guarantee that an user can only add a document that has the same id that its auth.uid.
For example, an user with id 1X0T6xC6hhRN5H02zLCN6SQtwby2 can only create/update the document /salesmen/1X0T6xC6hhRN5H02zLCN6SQtwby2 .
At app level, it's done by the following command (both for create and update).
this.db
.collection("salesmen")
.doc(user.uid)
.set(data)
.then(function() { //...
At Firestore level, I am trying the following rule, but it's not working (it results in Error writing document: Error: Missing or insufficient permissions.).
match /salesmen/{salesman} {
allow read: if true;
allow write: if request.auth != null && resource.id == request.auth.uid;
}
How can this rule be enforced ?
resource.id can only be used to refer to an existing document in Firestore. It doesn't work if the document being written doesn't exist yet.
Instead, you can use the salesman wildcard in your match expression to determine if the user can write a particular document, even if it doesn't exist yet:
match /salesmen/{salesman} {
allow read: if true;
allow write: if request.auth != null && salesman == request.auth.uid;
}
It looks like you can also use request.resource.id to reference the id of the document that possibly has not been written yet:
match /salesmen/{salesman} {
allow read: if true;
allow write: if request.auth != null && request.resource.id == request.auth.uid;
}
Though I can't find that explicitly discussed in the reference documentation as of this moment.

Firebase Storage Rules customMetadata not working

I made some rules to access shared files only for a group of people in Firebase Storage.
The way I do this is to put all the uid's in the customMetadata as keys
[uid: Value]
When I evaluate if the user can read and write the data, I do this:
service firebase.storage {
match /b/{bucket}/o {
match /{accountId}/{allPaths=**} {
allow write: if request.auth.uid in request.resource.metadata.keys() && request.auth != null;
allow read: if request.auth.uid in request.metadata.keys();
}
}
}
I can write successfully, but I just can't read the data.
I have tried all kind of ways:
request.metadata[request.auth.uid] == 'theValue'
request.resource.metadata[request.auth.uid] == 'theValue
request.auth.uid in request.metadata
request.auth.uid in request.resource.metadata.keys()
Nothing works.
You should use:
allow read: if request.auth.uid in resource.metadata.keys();
I see that you've tried request.resource many times. This variable is only accessible when writing to Firebase Storage. When reading you must use resource directly.

Resources