Firestore security rules - checking for bool in a DB - google-cloud-firestore

I don't know if i am able to ask this question properly, but here it goes...
I have a firebase collection, which has the bool field 'isAdmin'. So now when users are logging in, i want to set a rule to check whether this field is 'true', before allowing them access to another collection i got.
Is there a way to do it?

It sounds like you want to define access based on a value in a document about the current user. For an example of that, have a look at the documentation on attribute based access control. Based on the example there:
service cloud.firestore {
match /databases/{database}/documents {
// For attribute-based access control, Check a boolean `admin` attribute
allow write:
if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin
== true;
allow read: true;
}
}
So here each user has a document in the users collection with the document ID the same as their UID, and the rule then gets that document and checks for the specific value you want.

Related

How do you allow only admins to read certain fields in a document in Cloud Firestore?

I have a collection of documents where there are some fields anyone should be able to only read and some fields where only the admin should be able read/write. My rules look something like
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /MyCollection/{document=**} {
allow read: if request.auth.token.admin == true;
allow write: if request.auth.token.admin == true;
match /publicField {
allow read: if true;
}
}
}
}
but I get a Missing or insufficient permissions err when I try to read the documents. How do I allow anyone to read the publicField of any MyCollection document?
It's not possible to change the access of individual fields in a single document. Match patterns can only target document paths, and don't know anything about document fields. If anyone can read a document, then they can always read the entire document.
If you want to change the permissions of some data in a document, they need to be split into another document in a collection that has appropriate security rules.
You might be interested in reading: The trade-offs between performance, cost, and security with Firestore

Firestore security rule to restrict writes to fields for both set and update?

I'm trying to write some Firestore security rules that only allow users to write to certain fields in their documents (e.g. email, gender, preferredName, address).
I wrote the following write rule to restrict access to specific fields:
match /databases/{database}/documents {
match /users/{userId} {
allow read: if userIsAuthenticated()
&& userIsAccessingTheirData(userId);
// Users can always write to specific fields
allow write: if userIsAuthenticated()
&& userIsAccessingTheirData(userId)
&& request.resource.data.keys().hasOnly(["preferredName","gender", "email", "address"]);
The rules works well for when we call userDoc.set in code, but it doesn't work when we call userDoc.update.
Using the Firestore rules emulator, I can see that when we call "set" the request.resource.data.keys() only has the fields that are being passed in the call, but when I call "update" all the fields of the document are in the key collection :-( which makes it impossible to filter.
Is there a way to write a security rule that restricts the fields like above that works for both set and update?
The request.resource variable represents the document as it will exist after the operation succeeds (if it succeeds of course). So request.resource does not just contain the fields that are being updated, but also the other values from the existing document.
It's always been possible to check if a field is being updated by comparing request.resource.data.fieldname with resource.data.fieldname.
But recently a new affectedKeys() function was introduced to security rules that shows just the delta:
// This rule only allows updates where "a" is the only field affected
allow update: if request.resource.data.diff(resource.data).affectedKeys().hasOnly(["a"]);
Also see the release notes for Firebase security rules.
you can access document fields using request.resource.data.{field}
for example if you want to restrict updating dob :
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /users/{user} {
allow update: if request.resource.data.dob == resource.data.dob;
}
}
}
This means that the document can be updated as long as dob hasn't changed which is what we are trying to achieve.

User to access only his/her document in Firestore

I'm struggling by setting the firestore rules.
As per the screenshot, I have collection for the user with dynamic document IDs.
I'm trying to set a rule for the user to access only his/her document.
FbId is facebook id (since it is the authentication way in my app)
userId is firebase id (not sure if it is important to save it or not)
Here is my current rule:
match /users/{document=**} {
allow read, write: if normalUser();
}
function normalUser() {
return request.auth.token.firebase.sign_in_provider == "facebook.com"
&& request.auth.uid != null;
}
This rule gives access to the authenticated user for the whole collection.
How can I set this rule? If there is anything I need to change in the structure?
Update: I don't want to change the documentid for the user collection to match userid because I have another collection where the user could have multiple documents; so this solution won't fit everything.
Thanks
My get method from the app was using facebook id in the query that is why it wasn't working.
The rule I'm using right now:
match /users/{userId} {
allow update: if request.auth.uid == resource.data.userId;
}
Thanks
It will be far easier for you to use the Firebase Authentication UID of the user as the ID of the document. The Facebook UID is not going to be available in security rules.
If you use the Firebase UID, you can then write rules that look like the ones in the documentation for user-based security. Click through and read where it says "Another common pattern is to make sure users can only read and write their own data". This is probably what you want to do.
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}

firestore security rules get subcollection document

I'm facing insufficient permissions for this firestore security check.
service cloud.firestore
{
match /databases/{database}/documents
{
match /events/{eventID}
{
allow read:
if get(/databases/$(database)/documents/events/$(eventID)/authorizations/$(request.auth.uid)).data.EVENT_READ == true;
}
}
}
the get document is hardcoded in the firestore database and the simulator returns true but for the actual query returns insufficient privileges.
I tested and moved the authorizations subcollection to the same level as users collection and it works. Am i missing out anything?
Additional testing: Reading the document directly does not result in insufficient privileges. I'm testing to see if it's an issue with listing but to my knowledge read should cover both get and list in firestore security rules.
Update: Listing seems to be the issue here. I tried to list the entire collection with only one document and it results in the insufficient privileges.
Works:
this.angularFirestore.collection('events').doc(eventID).valueChanges();
Doesn't work (updated):
this.angularFirestore.collection('events', query => query.where('admins', 'array-contains', auth.uid)).valueChanges()
My firestore database:
/events/event1_id
- field 1: some string
- field 2: some string
- admins: array of uid strings
/authorizations/<uid> #uid for $(request.auth.uid)
- EVENT_READ: true
Update 2: Updated the doesn't work query string which I tried out. It is intriguing that if i move the /authorizations sub collection out to be the same level as /events collections, the query will not fail.
Your first query works because it's accessing the events collection with a specific document. That specific document meets the rules criteria, because you've arranged for the get() to allow it.
Your second query doesn't work because it's attempting to get all of the documents in events collection. Your rule does not specifically allow that. It looks like you expect your rule to filter out the events that aren't allowed access based on the contents an unknown number of other documents. You need to be aware that security rules are not filters. Please click through to the documentation and read that section. The client must only request documents that are known to be readable according to rules. It can't depend on rules to filter documents that are not allowed.
If you want to be able to query for all events that the current user has access to, your current database structure will not work. You will need to put all the relevant information in the events collection itself. This means you should consider something like putting the UID of each user that's allowed to read the event in the document itself, then filter on that field. Or have some other collection that you can query in this way.

Firestore rule on condition from a different collection

I have a Firestore db with two collections: users and tournaments. The users have a 'participant' role and an 'admin' role and are indicated by 'isParticipant' and 'isAdmin' booleans in the user document:
/users/{userId}:
isParticipant: true
isAdmin: true
I have access rules set up for these collections like so:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow create, read;
allow update: if request.auth.uid == userId;
}
match /tournaments/{tournamentId} {
allow create, read, update: if request.auth.uid != null;
}
}
}
However, what I really want to do is restrict the creation of a tournament to a user that is of an 'admin' role. I am already doing this in code, but would like the added security of a db rule to prevent anyone but an admin user from creating a tournament.
Is there a way to reference the data element of a different collection within the rules syntax? Something like:
match /tournaments/{tournamentId} {
allow create: if resources.users.userId.isAdmin == true;
}
?
Thanks in advance.
You can access the data in a different collection by using the get function:
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true
For more information on this, see access other documents in the Firebase documentation, which is where I got the above example from.
One thing to note is that this requires an additional document read. If you'd store the fact that a user is an admin inside their profile as a custom claim, you could access it from request.auth without needing an extra read. E.g.
allow delete: if request.auth.token.admin == true
For more on this, see control access with custom claims and accessing the user token in the documentation.