I'm building a chat app with Firestore and each chat room has a map of participants like so:
The keys are the corresponding UIDs of the users. I want to use this map to decide which user can access a chat.
In my security rules, I currently check if the logged-in user's UID exists in this map to allow read and write access:
allow read, write: if request.auth.uid in resource.data.participants
But this only checks if the user is in this map, not if the value is actually true. I want to check that as well.
What I basically want is something like this:
allow read, write: if resource.data.participants.request.auth.uid == true
But this does not actually work. How do I use the current user's UID as the key in a map check?
Use square brackets to index into the Map using any string expression you want:
allow read, write: if resource.data.participants[request.auth.uid] == true
Related
I am developing a Flutter app and using Firebase as backend. In my app, each user needs o sign up to be able to use the app and the user's profile data is saved in a user_profile collection.
Each user's profile data is stored in a separate document in the collection.
The Document ID for each document is equal to the User ID, created by the Firebase Authentication when the user signs up for the first time.
And I also save the User ID in a field (named uid) in each document as well for the corresponding user.
For the security part, I want that each user may only read his/her own profile data. I set the following rule:
// Rules for User Profile data
match /user_profile/{any} {
allow read: if (request.auth != null) &&
(resource.data.uid == request.auth.uid) &&
exists(/databases/$(database)/documents/users/$(request.auth.uid));
Is it correct if I set my rules as given in the above example?
(1) The user needs to be authenticated
(2) The uid in the incoming request needs to be equal to the uid field in the corresponding document that the user wants to read.
(3) The document with the uid available in the request must exist in the corresponding document
I cannot make it clear to me if I am making the whole thing unnecessarily complicated. For instance, does the rule (1) do the same thing as rule (2)? While I have rules (1) and (2), does it add anything to have rule (3) as well?
Based on the following elements in your question:
The user's profile data is saved in a user_profile collection
The document ID for each (user's profile) document is equal to the User ID
You want that each user may only read his/her own profile data
the following read security rule should do the trick:
service cloud.firestore {
match /databases/{database}/documents {
// Match any document in the 'user_profile' collection
match /user_profile/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
// ...
}
}
}
In other words, since the document ID for each (user's profile) document is equal to the userId you don't need to use the field containing the userId in the security rule: The wildcard expression {userId} makes the userId variable available in rules, see the doc.
I am writing a chat application and am done apart from the security rules section. I am currently creating two documents for each message (one each for each user) I am okay with writing a document to my user Id but the database isn't allowing for a write in the other paired user Id.
I have tried by allowing the write if the userId is in the resource.data of the other file
match /message/{user}/{chatRoomID}/{messageId} {
allow read, write: if request.auth.uid == user || request.auth.uid in resource.data;
}
How can I make it so whenever a message is sent to the database it is only read and can be written by the specific user Ids??
Each message object has reference to who sent it (each user's object Id). Thanks in advance !!
While in is indeed an operator in security rules, this won't work:
request.auth.uid in resource.data
The in operator checks if a key exists in a map, where it is much more likely that you store the UID of the other user in the value of a field.
To check whether a certain field has a specific value, use something like this:
request.auth.uid == resource.data.senderID
I have these rules:
match /suuntoAppAccessTokens/{userName} {
allow create: if request.auth.uid != null && request.auth.token.firebase.sign_in_provider != 'anonymous';
match /tokens/{userID} {
allow read, write, create, update, delete: if request.auth.uid == userID && request.auth.token.firebase.sign_in_provider != 'anonymous';
}
}
match /{path=**}/tokens/{userID} {
allow read, write, create, update, delete: if request.auth.uid == userID;
}
That means that for the path /suuntoAppAccessTokens/dimitrioskanellopoulos/tokens/{userID} the current user should have access.
However, when I query the collection group like so:
return this.afs.collectionGroup('tokens').snapshotChanges();
I get a permission error.
Getting directly the document under tokes/{userID} works as expected.
What can I do so that the current user can run a collectionGroup query and get the items he is permitted to get based on my rules?
Your rule is expecting that the security rule will filter all the documents from all of the tokens collection so that only the current user's documents will be read. This is not possible with security rules. Security rules are not filters. From the documentation:
When writing queries to retrieve documents, keep in mind that security
rules are not filters—queries are all or nothing. To save you time and
resources, Cloud Firestore evaluates a query against its potential
result set instead of the actual field values for all of your
documents. If a query could potentially return documents that the
client does not have permission to read, the entire request fails.
You will need to change your query to that the client is only requesting documents that are fully expected to be readable by the current user. Unfortunately, it's not possible for me tell if this is possible with your current schema. The ID of the document {userId} can't be used in a collection group query to filter the documents. So, both you must ensure that both of the following criteria are met:
You will need some field in the document that you can filter on to get this job done.
You will need to adjust your security rule to match exactly what the client is asking for.
I suggest storing the uid of the user in the document with the token, the same as {userId} in the rule. You can query it like this:
collectionGroup('tokens').where("uid", "==", uid)
Be sure that the client passes in the uid correctly
Also, you will need to make sure that the rule is granting access by the exact same criteria:
match /{path=**}/tokens/{userID} {
allow read, write, create, update, delete:
if request.auth.uid == resource.data.uid;
}
This will only allow access to the document if its uid field is the same as the auth uid, which is exactly what the client is asking for.
I want to be able to limit users to only list documents they've created.
The user id is stored in the user field
Obviously I can do
db.collection('projects').where('user', '==', firebase.auth().currentUser.uid)
.. but any tech savvy user could just remove the filter and get everything.
I've limited access in rules like
match /projects/{project} {
allow read,update: if request.auth.uid == resource.data.user;
allow create;
}
But this doesn't work, you can't list at all.
Is there a way of doing this without creating a subcollection of the user's entry in the user collection? I'd really prefer to have them all in one place.
Surely this is an extremely common scenario.
Assuming you stored the user_id in the field "user" in firestore. You can use
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
db.collection("projects").whereEqualTo("user", uid)...
After ellipses you can use .get() with onSuccesss or onComplete or add Snapshot listener.
How do you get all documents in a collection, for which the current user has read permissions?
Trying to get all documents results in a permissions error, because it includes attempts to read documents where the user does not have permission (rather than returning the filtered list of documents).
Each user in this app can belong to multiple groups. Reads are locked down to the groups that they have been added to.
match groups/{group} {
allow read: if exists(/databases/$(database)/documents/groups/$(group)/users/$(request.auth.uid));
}
Here's how this would look with a hypothetical subcollection-contains-id operator.
firestore()
.collection("groups")
.where("users", "subcollection-contains-id", user.uid);
As a temporary workaround I've moved this logic to a cloud function. Here's a shorthand of how it works.
for (let group of firestore().collection("groups")) {
let user = firestore.doc(`groups/${group.id}/users/${uid}`);
if (user.exists) {
// Send this group id to the client
}
}
How can I keep these concerns together and move this logic to the client side without relaxing the security rules?
You could add owners field in the documents inside a collection
owners: ["uid1", "uid2"]
Then, you could get all the posts with uid by searching with array_contains
ref.where("owners", "array-contains", uid)
In rules, you could add sth like these:
allow read: if request.resource.data.owners.hasAny([request.auth.uid]) == true
allow update: if request.resource.data.owners.hasAny([request.auth.uid]) == true