firestore security rules : deny profile update but allow rating update - firebase

I have collection called 'services' inside every document I have 4 fields :
name
description
userID
Rating
and I want to give only the user to change his profile except rating I want to make it accessible (can update) to everyone , I check some problem like that and I get one solution that is create sub collection in profile doc I call it "rating" that contain a doc with the name "rate" and I make only this sub collection accessible to update from everyone ,
but I don't know how to to get subcollection data from a doc any solution plz with this rating problem :
My security rules after creation a sub collection for rating :
match /{category}/{serviceid} {
allow read,create;
allow update : if resource.data.userID == request.auth.uid;
match /rating/rate {
allow read,write;
}
}
I get all services with const docs =db.collection("categoryname").get() :
and I fetch every item data like that :
docs.foreach(doc => doc.data().name)
How can I get subcollection("rating") data from the doc?

You realize that with these rules, you allow everyone access? Even people unauthenticated would be able to make unlimited writes. Is that what you really want?
To answer your question, to access to the /rating/rate document you could just do:
let category = "categoryname";
db.collection(category).get()
.then(function(querySnapshot) {
querySnapshot.foreach(
function(doc) {
let serviceid = doc.id;
db.collection(category).doc(serviceid).collection("rating").doc("rate").get()
.then(function(doc) {
... // do whatever
}
}
)
}
You can also check the Official Documentation to get a document.

Related

Firebase firestore security rule for collectionGroup query

I am trying to query and filter a collectionGroup from the client doing this:
const document = doc(db, 'forums/foo');
const posts = await getDocs(
query(
collectionGroup(db, 'posts'),
orderBy(documentId()),
startAt(document.path),
endAt(document.path + '\uf8ff')
)
);
My auth custom user claims looks like this:
{ forumIds: ['foo'] }
The documentation tells me to add the following security rule:
match /{path=**}/posts/{post} {
allow read: if request.auth != null;
}
But this is a security breach as it means that anyone can read all of the posts collections. I only want the user to read the posts in its forums. Is there no better way to secure a collectionGroup query?
(1) I have tried:
match /{path=**}/posts/{post} {
allow read: if path[1] in request.auth.token.forumIds;
}
but I get this error: Variable is not bound in path template. for 'list' # L49.
(2) I have also tried:
match /{path=**}/posts/{post} {
allow read: if resource.__name__[4] in request.auth.token.forumIds;
}
but I get this error: Property __name__ is undefined on object. for 'list' # L49.
I have also tried debugging the two previous security rules with debug and both of them return true.
Based on your stated requirements, you don't want a collection group query at all. A collection group query intends to fetch all of the documents in all of the named collections. You can only filter the results based on the contents of the document like you would any other query.
Since you have a list of forums that the user should be able to read, you should just query them each individually and combine the results in the app. Security rules are not going to be able to filter them out for you because security rules are not filters.
See also:
https://medium.com/firebase-developers/what-does-it-mean-that-firestore-security-rules-are-not-filters-68ec14f3d003
https://firebase.google.com/docs/firestore/security/rules-query#rules_are_not_filters

Firestore Database security Rules: Problem with collection restriction based on array from another collection

we got some problems with our security rules settings.
Our code looks like this
service cloud.firestore {
match /databases/{database}/documents {
function sameCompany(){
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.companyId == resource.data.companyId
&& request.auth.uid in get(/databases/$(database)/documents/companies/$(resource.data.companyId)).data.users;
}
match /orders/{orderId} {
allow read: if request.auth != null && sameCompany();
}}
The collections have the following structure:
orders/{orderId}
users/{userId}
companies/{companyId}
each doc in orders has one companyId.
each doc in users has one companyId.
each doc in companies has an array containing the userIds that are in the company
Only users that have the same companyId as the order and also if their userId is in the array from the company should be able to read the orders.
This is to have a double security that users cant change their companyId to read orders from another company
The first part before the && works just fine. If I manually put an Array after "request.auth.uid in " that contains the userId that is querying it also works fine, but somehow it doesnt work like this and I cant find the issue here.
Can someone help? Thank you for your time!
Edit1:
Thank you for your responses so far.
So the query looks like this:
query(
collection(db, "orders"),
where("companyId", "==", user.companyId)
)
We save in local Storage the user with the companyId. Without the second part after the && the data is retrieved like it should be, but with it nothing is shown. We want to double check if the users has the same companyId and is in the users array in the company in case someone tries to guess the companyId from another company and somehow can access the data. Like this you would need to know the companyId and the userId.
The data could simply look like this:
users doc with uId1:
{companyId: cId1, ...}
orders doc with dId1:
{companyId: cId1, ...}
companies with cId1:
{users: [uId1], ...}

Firestore security rules with reference fields

I am a bit stuck here as there is no way to debug those rules. I'd appreciate help with below rules.
I want to access:
/modules/module-id/sessions/session-id/parts/
The comparison with null in the first part of hasCompletedPrerequisiteSession() works well, the second part doesn't!
The path /modules/moduleId/sessions/sessionId/prerequisite points to a reference field.
service cloud.firestore {
match /databases/{database}/documents {
function hasCompletedPrerequisiteSession(moduleId,sessionId) {
// this part works well
return getPrerequisiteSession(moduleId,sessionId) == null ||
// !!! this part does not work !!!
hasCompleted(getPrerequisiteSession(moduleId,sessionId).id);
}
function getPrerequisiteSession(moduleId,sessionId) {
return get(/databases/$(database)/documents/modules/$(moduleId)/sessions/$(sessionId)).data.prerequisite;
}
function hasCompleted(sessionId) {
return exists(/databases/$(database)/documents/progress/$(request.auth.uid)/sessions/$(sessionId));
}
match /modules/{moduleId}/sessions/{sessionId}/parts/{partId} {
allow read: if hasCompletedPrerequisiteSession(moduleId,sessionId);
}
}
}
(If I store the session ID as a string instead of a reference to the session, it works fine.)
Edit
Questions
Reference field in security rules. Assuming modules/moduleId/owner points to a field of the type reference. What is the proper way to get the id of the referenced document?get(../modules/moduleId).data.owner.data.id or get(../modules/moduleId).data.owner or something else?
From Firebase support:
It seems that in your use case, you want to get the document name (sessionId) from the value of your reference field (prerequisite), unfortunately, this is not currently supported by Firestore security rules. I would suggest that you store only the sessionId as String on your prerequisite field, or you can also add String field for the sessionId. Keep in mind that the exists() and get() functions only allow you to check if a document exists, or retrieve the document at the given path.
It might be that around getPrerequisiteSession, after using get to pull the object by ref path, you had to use .data first before referencing the id field. Of course, id field needs to be stored as an object field.
For example, in my case I needed to allow user to add a message into a chat only if they're the owner of that chat room. There are 2 "tables" - chats and chat_messages, and chat_messages relate to a specific chat through chatId field. chats objects have ownerId field.
The rule I've used goes like this:
match /chat_messages/{itemId} {
function isOwner() {
return get(/databases/$(database)/documents/chats/$(request.resource.data.chatId)).data.ownerId == request.auth.uid;
}
allow read: if true;
allow create: if isOwner();
}

Custom security rules for Cloud Firestore

I want to create a Cloud Firestore realtime database containing groups which users can join and share information to other members of their group. I want to preserve users anonymity so the way I see it to be implemented is:
Group creator generates a group key in format XXXX-XXXX-XXXX-XXXX
Those who want to join must have the group key which they enter in the app, after that they should be able to read, create and update data in that group
So basically the data structure is something like this:
/groups/ : [
//groups as documents
"ABCD-0000-0000-0001" : { /*group data model*/ }
"ABCD-0000-0000-0002" : { /*group data model*/ }
"ABCD-0000-0000-0003" : { /*group data model*/ }
]
The question is, what security rules should I write to permit users to read, create and update data ONLY in the group they belong to (have its group key)? At the same time, how to forbid users from finding out other groups' keys?
Your group structure can remain as is-
groups (Collection) : [
//groups as documents
"ABCD-0000-0000-0001" : { /*group data model*/ }
"ABCD-0000-0000-0002" : { /*group data model*/ }
"ABCD-0000-0000-0003" : { /*group data model*/ } ]
And to maintain the access, you can have another separate collection named group_users as-
group_users(Collection)/ <group_id>(Document)/ members(Collection)/ :
uid_1 (document)
uid_2 (document)
...
Now the rule to allow can be like-
service cloud.firestore {
match /databases/{database}/documents {
match /groups/{group_id} {
allow read, create, update: if exists(/databases/$(database)/documents/group_users/$(group_id)/members/$(request.auth.uid));
}
}
}
When a member joins the group, you will have to add the user's uid to that new collection-
group_users/<group_id>/members
For admins, you can have a similar collection, and uid will be added when the admin creates the group-
group_users/<group_id>/admins
Instead of having a separate collection outside of groups collection, you could have a collection within group itself as an alternative solition, then you will have to modify the group data model to some more extent.
Also FYI, each exists () call in security rules is billed (probably as one read operation).
And here is a detailed documentation explaining almost every possible aspect of firestore security rules.
You can save the group ID in the user's profile. Then create a rule which only allows CRU permissions if that group ID exists.
db.collection.('users').doc({userId}.update({
ABCD-0000-0000-0001: true
})
match /groups/{groupId} {
allow read, create, update: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.$(groupId) == true;
}

Firestore data duplication

I'm trying to setup a friend system in Firestore. My data model looks like this at the moment:
collection("users") ->
document("user1")
document("user2")
...
A document in the users collection contains data like the name, email... of the user. I'd like to enable a user to have friends now, but I'm unsure about the best way to model this.
So, I'd for sure add a friends field in the documents of the users, but what should this field contain? My first thought was a pointer to a new collection called friends in which the documents are users. Something like this:
collection("users") {
document("user1") {
name:user1,
friends: -> collection("friends") {
document("user2"),
...
}
}
}
This seems reasonable, but that'd mean that I'd have a lot of duplicate data in my database because each user that has friends will be duplicated in a friends collection. Should I worry about this or is this normal in a Firestore database structure?
Would it perhaps be possible to point to a document in the users collection from the friends collection? Something like:
collection("users") {
document("user1") {
name:user1,
friends: -> collection("friends") {
document, -----
... |
} |
}, |
document("user2")<-
}
Or should I throw away the thought of using a collection for friends and just keep a list with uids of all friends of the user?
Seems you are using two separate collections for users and friends first all you can do it by one collection. But I don't want to go there may be there was another scenario.
As your separate collection way, you can design your friends collection model to meet no duplication:
{
name : 'Name',
email : 'email#mail.com'
has_connected : {
'user1' : true // here you can use anyother unique key from user
}
}
The thing is that firestore recommend this types of design for query and for faster performance you can make that has_connected key as index.
In this approach, you have to check during adding new friend by email or any other unique key. if exists then just put another key into has_connected with the respective user. e.g user2 : true.
Finally, for fetching all friends for a user you have to do a query like this: e.g: in javascript
let ref = firebase.firestore().collection("friends");
ref
.where(`has_connected.${username}`, "==", true)
.get()
.then(//do your logic)
.catch()
Thanks

Resources