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.
Related
I want to secure a collection of accounts documents with a field access.users that contains an array of user DocumentReferences which are allowed to access an account document in the collection.
accounts.access.users = [ //array of user document references ]
In my query (JS client library) I am setting the query:
db.collection('accounts').where('access.users', 'array-contains', userRef)
To secure the data, I want to write a rule:
function userHasAccountAccess () {
let user = getUser(); // returns users document reference based on auth uid
// - here - need to check that the users document reference was requested by the query ie - that `request.query` contains the `access.users` field and that value of this filter in an array/list which includes a reference to the users' document
}
match /accounts/{docId} {
list: if userHasAccountAccess();
}
... but it seems from the docs that the only properties available on a query are limit, offset and orderBy, so then I am unable to test or secure this way.
So how are others securing their data in this type of access role ACL scenario for LIST type requests?
So after some digging, I found the answer.
Where posting a query (LIST) request as so:
db.collection('accounts').where('access.users', 'array-contains', userRef).limit(5)
... it seems that limit, orderBy and offset become properties of request.query in the security rules, but the where filters become properties of resource.data.
This is confusing because a) resource.data is usually a map of document data being posted (ie when saving records) and b) the docs describe resource.data as such.
db.collectionGroup('private')
.where('members', 'array-contains', userId)
.get()
.then(...)
This query fetches documents successfully if the relevant security rule is set like:
match /{path=**}/private/{document} {
allow read: if request.auth.uid in resource.data.members;
}
However, the similar rule below prevents the same query unexpectedly.
match /{path=**}/private/allowed {
allow read: if request.auth.uid in resource.data.members;
}
In this database,
private subcollections exist only under documents in the rooms collection.
Every private has only a single document with the ID "allowed".
This means /rooms/xxxxxxxx/private/allowed is the only possible path existing, where xxxxxxxx is an auto-assigned document ID.
Therefore specifying the path as /{path=**}/private/allowed looks correct to me.
In fact, "get" queries work in simulations in the playground, so is it a restriction only for collection group queries, or am I doing anything wrong?
FYI, more detailed database structure is described in another question of mine here.
Yes, it is required.
When you perform a collection group query, it's not possible to call out a specific document id in the query (e.g. "allowed"). The query is explicitly asking to consider all of the documents in all of the subcollections of the given name ("private"). Therefore, the rules must allow for those documents to be considered by adding the trailing wildcard.
You can certainly add a filter to the query if you want to get only certain documents with certain field values, but that filter can't be enforced in the rules.
I know how to check if an array contains a given string (as explained for example here). My requirement however is different: I have a document with an array updatedByHistoryArray written at server side that contains the history of the ids of all users who updated such a document, for example [id1, id2, ..., idn].
I would like to allow a delete operation for this document only if the latter has been updated exclusively by the user who wants to delete it.
So, for example, if a user with id24 wants to delete a document, the updatedByHistoryArray of this document has to be [id24, id24, ..., id24].
Is it possible to implement this requirement in the security rules of Firestore?
It sounds possible. Try using hasOnly() to see if the list field contains only a single user ID.
resource.data.updatedByHistoryArray.hasOnly([request.auth.uid])
I have few questions about collection group:
Is there way to execute request for collection group in Firestore simulator?
Can I add additional parameter for collection group rules for example the following rule is used for collection group
match /{prefix=**}/access/{email} {
allow read: if isSignedIn();
}
before access collection i have one more collection with user id, is is possible to add it as parameter to do some validations?
No, there currently is no way to simulate a collection group query in the Firestore console. (Actually there is no querying at all except individual document gets.)
There is no way, using security rules, to know any of the other path elements that come before access in the case you're showing. The prefix wildcard actually will not even contain any data at the time of execution.
I searched everywhere for this but I can't find anything on that matter. If you don't predefine fields in MongoDB, couldn't a user with Insert permission then Insert a document with every kind of field he wants? Via Collection.insert? If I am thinking correctly here, is there a way to restrict this?
You can restrict inserting any kind of fields in these two ways:
Use collection.allow/deny(http://docs.meteor.com/#/full/allow) - the insert callback has a doc parameter, which contains the exact document that user wants to insert - you can check the content of it and deny the insertion if you spot fields that are not allowed.
Use SimpleSchema (https://github.com/aldeed/meteor-simple-schema) and Collection2 (https://github.com/aldeed/meteor-collection2) packages to define the schema and attach it to your collection - it will prevent the insertion if a document has additional/missing fields (or fields of not expected type).
This is my personal preference. Because fieldNames param in
Collections.update(userId, doc, fieldNames) only gives top-level fields in doc. So if you are having nested fields it is very hard to track.
So I don't use collection allow/deny rules. Without allow deny rules Collections.insert/Collections.update does nothing on client. Instead I am using Meteor methods to update/delete documents to collections, so I can decide which exact fields should update/insert.