Firestore rule to access subcollection raising Missing or insufficient permissions - firebase

I am struggling with Firestore rules to allow access to some resources in a subcollection.
I have some requests documents, that may present a sub-collection named status. My current rules are something like that:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// I use it to check if user is signed in
function userSignedIn() {
return request.auth != null;
}
match /requests/{requestId} {
// I use it to check if user is the owner of the request
function userCanAccess () {
return userSignedIn() && request.auth.uid == get(/databases/$(database)/documents/requests/$(requestId)).data.userId;
}
// allow read to logged user who own request
allow read, update: if userCanAccess();
// anyone can create a new request
allow create: if true;
// no one can delete the request
allow delete: if false;
match /status/{statusId} {
// allow read and update of status to logged user who own request
allow read, update: if userCanAccess();
// anyone can create the status
allow create: if true;
// no one can delete the status
allow delete: if false;
}
}
}
}
My very first way to check if user had access was by request.auth.uid == resource.data.userId, however it only works for requests documents, not for status documents since they do not have the userId field.
I'm using flutter to make the request with the code:
FirebaseFirestore.instance
.collection('requests')
.where('userId', isEqualTo: user.uid)
.orderBy('requestedOn', descending: true)
.snapshots()
.listen((snapshot) {
// Here I read requests and their status
});
However I'm getting Error: [cloud_firestore/permission-denied] Missing or insufficient permissions.. How can I solve (or debug) it? Using rules playground in firestore, with the exact same uid, I am able to complete the reading.

I'm wondering if the get() call may be causing problems. Try this:
function userCanAccess () {
return userSignedIn() && request.auth.uid == requestId.data.userId;
}

Related

Firestore: Prevent write/overwrite/update field once it is added

I have a collection in which I am storing user requests in documents having documents ID as user's email. In the document, I am creating fields the key for which is being generated at client side.
Now, the problem that I am facing is that user can overwrite the existing field/request in the document if the key matches which I don't want to happen.
What I tried was to use this rule which unfortunately does not work
resource.data.keys().hasAny(request.resource.data.key();
So how can I achieve this?
Below are the screen shot of the firestore data and the current security rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /roles/{userId}{
allow read: if isSignedIn() && hasId(userId);
}
match /requests/{email} {
allow read, update: if isSignedIn() && hasMail(email)
}
//functions//
function hasMail (email) {
return request.auth.token.email == email;
}
function hasId (userId) {
return request.auth.uid == userId;
}
function isSignedIn () {
return request.auth != null;
}
function getUserRole () {
return get(/databases/$(database)/documents/roles/$(request.auth.uid)).data.role
}
}
}
You can check if a resource already exists. Here an example:
allow write: if resource == null // Can create, not update
Use that to restrict any edit or update of the data. If you have additional rules you can granulate them to update, delete and create.

How to check if document exists in firestore security rules list request?

I have two root collections, users and workspaces. A user document has a workspaces array with the workspace document IDs the user is a part of. Below is the security rule I try to use. I had two approaches, one is to get the user document from the root collection and check its workspaces array for the workspaceId the other is to check for existance of the userId in the members subcollection. Both end up throwing the same error: FirebaseError: Null value error. for 'list' # L15
If I separate the allow read into allow get and allow list and just write true for list it fixes the error but obviously I want to restrict the access so users can only get their own workspaces (where they are members).
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /workspaces/{workspaceId} {
allow read: if isLoggedIn() && userIsInWorkspace(workspaceId);
}
function isLoggedIn() {
return request.auth != null && request.auth.uid != null;
}
function userIsInWorkspace(workspaceId) {
return exists(/databases/$(database)/documents/workspaces/$(workspaceId)/members/$(request.auth.uid));
}
}
}
Approach with get:
function userIsInWorkspace(workspaceId) {
let workspacesOfUser = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.workspaces;
return workspaceId in workspacesOfUser;
}
UPDATE:
I tried implementing it with custom claims like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if isLoggedIn() && request.auth.uid == userId;
}
match /workspaces/{workspaceId}/{document=**} {
allow read, write: if isLoggedIn() && userIsInWorkspace(workspaceId);
}
function isLoggedIn() {
return request.auth != null && request.auth.uid != null;
}
function userIsInWorkspace(workspaceId) {
return workspaceId in request.auth.token.workspaces;
}
}
}
So my concept was that I have users and workspaces as rootcollections and workspaces has subcollections such as teams and reports and so on. I still get the same null value error. When this comes up is in the above mentioned error so list requests does the error. My use case is that when you are logged out from the app the workspace slug that is stored on every workspace entry in firestore gets added az a query parameter so the user can be redirected back to the exact workspace. To make this happen I do a list request so basically I query the workspaces collection where the slug is the given slug from the url.
const workspaceSnapshot = await db
.collection('workspaces')
.where('slug', '==', this.$route.query.slug)
.limit(1)
.get()
This request creates my error but from this I cannot make out anything. I would suppose that when I give a condition for reads and writes that includes get and list as well.
Queries are case-sensitive. You said you had two root collections, namely Users and Workspaces, but you are querying against users and workspaces. This will not yield results. Try changing your query:
function userIsInWorkspace(workspaceId) {
let workspacesOfUser = get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.workspaces;
return workspaceId in workspacesOfUser;
}

Firestore rules access to parent document

So i'm making an app with a friends system and trying to set up rules for firebase to handle reads & writes if the users is friends or not.
I'm very stuck at a particular call that i just don't have any idea on how to make.
My firestore is structured as follows:
users/userUID/places/documentsofplaces
each userdocument have some fields of the usual information, name, username, etc. and an array of friendsUID.
I have managed to get the first part down, that a user can only read and write if it's UID matches the documentUID, and looking in the friendslist a friend can only read but not write.
The next part, in the places collection, just throws me off, how can i get the parent document and compare the userUID to a UID in the friendslist?
This is what i have so far:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// Allow write and read if user, and read if friend
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend();
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend() {
return (request.auth.uid in resource.data.friendsList);
}
}
match /users/{userId}/places/{documents} {
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend(userId);
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend(userId) {
return (request.auth.uid in get(/databases/$(database)/documents/users/userId.resource.data.friendsList));
}
}
}
}
Any help is greatly appreciated!
Your document get() should look more like this:
get(/databases/$(database)/documents/users/$(userId)).data.friendsList
You have to use variables with $(var) notation inside the document path. get() returns a Resoruce object with a data property. I suggest reading over the documentation for accessing other documents for more details.

How to make authenticated Firestore queries when handling a webhook in a firebase function?

My app receives a webhook from a 3rd party service, telling it that data is ready to be queried.
The webhook payload includes:
UserId
ObjectId of the object whose data is ready.
In order to query the data, I need to get an access token:
const { accessToken } = await db
.collection('users').doc(userId)
.collection('objects').doc(objectId)
.get();
// then I can:
fetchUpdatedData(objectId, accessToken)
However, I have rules in place to require that users' data may only be accessed by the user:
# `firestore.rules`
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Reject by default
match /{document=**} {
allow read, write: if false;
}
// Users can edit their own document
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
// Users can manage their subcollections
match /users/{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
What's the typical way to do this?
It's not possible to use security rules to limit queries coming from backends like Cloud Functions that use any of the server SDKs. Server SDKs initialize with a service account, which always bypass security rules. You're going to have to duplicate the relevant checks from the rules in your backend code to check if the query should be done on behalf of the user.

Cloud Firestore - still get "any user can write to your entire database" error

I've added rules to my Cloud Firestore database but still receive these issue messages:
any user can read your entire database
any user can write to your entire database
See the rules below.
Indeed, any user can read and write into "users" collection, otherwise I won't be able to log in/register a user. How to solve the issue?
Thanks
The problem with your current rules is that it allows any user to read/write any other user's user document. You need to restrict it a bit more to look at the userId of the documents. Something like this
service cloud.firestore {
match /databases/{database}/documents {
// Ensure the user is authenticated
function userIsAuthenticated() {
return request.auth != null && request.auth.uid != null
}
// Ensure the user owns the existing resource
function userOwnsResource() {
return userIsAuthenticated() && request.auth.uid == resource.data.userId
}
// Ensure the user owns the new resource
function userOwnsNewResource() {
return userIsAuthenticated() && request.auth.uid == request.resource.data.userId
}
match /users/{userId=**} {
allow read, update, delete: if userOwnsResource();
allow create: if userOwnsNewResource();
}
}
}
userOwnsResource checks that the user can access an existing resource. userOwnsNewResource checks that they can create a new document for their userId. You can obviously reuse these functions in other rules which is handy.

Resources