Having read through the documentation, I believe the rules below should allow me to lock all documents inside a database to their respective owners. So I have a property on a document called owner, which should compare the auth uid and set permissions based on the ownership of the document. I'm still receiving an Insufficient permissions error though. Someone can explain to me why this is happening?
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid == resource.data.owner;
}
}
}
Edit
Let's assume I have a task object in a todo app. All the tasks are stored in the same tasks collection: /databases/{database}/documents/tasks/*
tasks/arYBG3ydXW: {
title: 'A task',
owner: uid,
}
I want to make sure that only the owner of the document can read or edit that document. However, first error already happens when trying to read the tasks from the collection. To be clear: I created some documents without rules setup, so I was initially able to write to the database without enforcing restrictions.
query(collection(db, 'tasks'), where('owner', '==', uid))
#firebase/firestore: Firestore (9.9.3): Uncaught Error in snapshot listener: {"code":"permission-denied","name":"FirebaseError"}
Also, I'm questioning now whether this is the best way to structure my data in this scenario. What would you consider to be "best-practice", approach 1 or 2 (assuming multiple users are going to access the same database)?
Approach 1: (with a prop indicating to whom a doc belongs)
/databases/{database}/documents/tasks/*.{uid}
/databases/{database}/documents/workspaces/*.{uid}
...
Approach 2: (all docs for a user in a separate collection)
/databases/{database}/documents/uid/{tasks},{workspaces}, ...
As pointed out by Frank in the comments from the original question, looking up the UID at execution time fixes the issue.
So instead of doing this:
query(collection(db, 'tasks'), where('owner', '==', uid))
do this:
query(collection(db, 'tasks'), where('owner', '==', getAuth().currentUser.uid))
Related
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.
I'm facing a permission denied error when querying firestore, when I have introduced a rule. I have narrowed down my complex rule and filter to the below 2 examples of which one query works, and one doesn't. I do not understand what is wrong with my failing query.
From https://cloud.google.com/firestore/docs/security/rules-query I understand that a rule is not a filter. According to this document: "If a query could potentially return documents that the client does not have permission to read, the entire request fails.".
Baring that in mind, I have been iterating over my rule, filter and data, and come with the below:
The data:
I have NO data in my collection called "MyCollection". As a matter of fact, the collection "MyCollection" has never existed.
The rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /MyCollection/{id} {
allow read: if (
(resource.data.readAccess == 0)
)
allow write: if (true)
}
}
}
My failing query (where I have the permission denied error):
Firestore.instance.collection('MyCollection')
.where("readAccess", isLessThanOrEqualTo: 0)
.getDocuments()
.then((_) => print("Success!"));
When I run this query, I get the following error:
W/Firestore(12491): (21.3.0) [Firestore]: Listen for Query(MyCollection where readAccess <= 0) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
My successfull query:
(The only difference in this query is that I replaced "isLessThanOrEqualTo" with isEqualTo)
Firestore.instance.collection('MyCollection')
.where("readAccess", isEqualTo: 0)
.getDocuments()
.then((_) => print("Success!"));
Comments:
I have the same results when I do populate MyCollection with data.
It looks like the query is validated against the rule, not the "potential return documents" as the document https://cloud.google.com/firestore/docs/security/rules-query states. If this is the case I wonder how I will be able to translate the following rule into a filter:
(resource.data.readAccess == 0) ||
((request.auth != null) &&
(resource.data.readAccess <= get(/databases/$(database)/documents/App/$(resource.data.appId)).data.group[request.auth.uid])
)
This rule is fairly similar, except that it validates the readAccess level of a document against the group access level in the "App" document for that data's app, for the logged on user. If I can't match the query for a simple rule, I can't imagine what I need to do for this complex rule.
Please advise. Many thanks.
With security rules, the query must exactly match the rules. The behavior you're observe is exactly what I would expect.
With a rule like this:
allow read: if resource.data.readAccess == 0;
That means the query must be filtered exactly like this;
where("readAccess", isEqualTo: 0)
Nothing else will satisfy this rule. It's absolutely demands that the query filter for exactly the value of 0 on the readAccess field. It's not clear to me why you're expecting a different outcome.
Your query suggests that the client provide its own "access" to the collection. Note that this is not secure. You can't depend on client apps self-reporting their own level of access in a database query. Something else on the backend needs to determine if the app is allowed to make the query.
Typically, Firebase Authentication is used to determine who the user is, then allow access based on what that user is allowed to do. You could store the user's permissions somewhere in another document, and use the contents of that document to determine what they can do. Or perhaps use custom claims. But you can't trust the the user pass their own permissions.
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
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.
I have some trouble with setting up my rules for a firestore project. I try to learn the database setup but can't find any solution for this. So there's no problems when i try to get a document from my collection "lists". But when i try to get all of the documents in the collection "lists" xcode tells me "Missing or insufficient permissions".
My goal is to have users that are able to create documents in collection "lists" but they can only read the documents in "lists" where they appear in the document array "members".
Right now I can add documents in collection("lists") without any problem but I can't read them. I can only read them one by one from xcode with a specific target.
Any tips or ideas?
service cloud.firestore {
match /databases/{database}/documents {
match /{documentId} {
allow read: if request.auth.uid != null;
}
match /lists/{docId} {
allow write: if request.auth.uid != null;
allow read: if request.auth.uid in resource.data.members
}
}
}
Xcode.
//working
let docRef = db.collection("lists").document("j0hHA5TLPETf6JRMbC1s")
docRef.getDocument { ...
//not working due to permission failed
db.collection("lists").getDocuments() { ...
Your rule doesn't work because it assuming that the rule will filter out all the documents that don't match the rule. This is not how security rules work. Security rules are not filters. From the linked 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.
The client must only request documents that would satisfy the rules. Right now, the query is asking for ALL documents in the lists collection, regardless of whether or not the client has access to them. This is going to fail the security rule, because it's attempting to read documents that it doesn't have access to.
What you need to do instead is make your query only request documents that are readable by the user. This means that you should probably be using an array-contains filter on the client to match only documents that the rule would allow it to read.
Actually, you are on the right path, I think if you change your code like this, it will work.
Instead of this:
match /{documentId} {
allow read: if request.auth.uid != null;
}
Use this:
match /lists {
allow read: if request.auth.uid != null;
}
So when you try to access lists without documentId, it will check the auth.uid.
But when you try to access a document ex. lists/1, it will check whether that user exists in the array.