firestore security rules syntax for - google-cloud-firestore

what is the syntax for using the auth.uid as item path in firestore
in firebase it would be this:
data.child(auth.uid).val() == 'admin'
I tried in firestore this, but is not working
resource.data.request.auth.uid == 'admin'
thans

resource.data is an object containing the data of document being read/written. If you are trying to read values of a field in that document then try:
allow read: if resource.data[request.auth.uid] == 'admin'
This rule will allow read operation if the field request.auth.uid is admin.

Related

Firestore Security Get with a Where Query using Array Contains produces a permission denied

I've got a Document structure where I have a collection of accounts (_accounts), and each account document in the collection has a subcollection called allowedusers. The documents within allowedusers has a document of each user that has access to the account. Each account document also has field of an array of string of the userids which I'm using to query using Array Contains.
My Firestore rules to ensure that each read is checked against the allowed users is :
match /_accounts/{accountid}{
allow read: if request.auth != null && get(/databases/$(database)/documents/_accounts/$(accountid)/allowedusers/$(request.auth.uid)).data.allowed == true
}
Dart Code from Flutter:
QuerySnapshot querySnapshot = await firestore.collection('_accounts').getAccounts()
.where('userids', arrayContains: _user.id)
.getDocuments();
The above query is producing a Permission Denied :
PlatformException (PlatformException(Error performing getDocuments,
PERMISSION_DENIED: Missing or insufficient permissions., null))
Alternate methods that I've attempted:
Security Rule :
match /_accounts/{accountid}{
allow read: if request.auth != null && resource.data.userids == request.auth.uid
}
Dart Code:
QuerySnapshot querySnapshot = await firestore.collection('_accounts')
.where('userids', arrayContains: _user.id)
.limit(5)
.getDocuments();
For testing purposes I've only got two documents in the _accounts collection, so I'm assuming that the permission denied is coming from hitting the limits when using get within the security rules.
Is there a way of applying security rule and query the collection like this ?
The first security rule is rejecting the query because security rules are not filters. Read more about what that means. It's important to understand this concept, so be sure to read and understand the documentation.
Security rules will only allow a query if it can determine that the query will only find documents that are allowed. It will not check each individual document in the collection for permission and filter out the ones that don't pass the rules. That would not scale at all.
The second rule is rejecting access because it's not correctly checking the array field. If userids is an array field, you can't use an equality expression to compare it with a string as you are with resource.data.userids == request.auth.uid.
If you want to make sure that the user's UID is contained within a document, you will need to treat the field like a list object, and use list operations on it. Use hasAny for that.
allow read: if request.auth != null && resource.data.userids.hasAny([request.auth.uid]);

check for absent field with firestore security rules

I am running a cloud function triggered by an onCreate event. This function required the userID, so after reading this post:
https://stackoverflow.com/a/50842161/4484332
.. i am passing the userId in the created document.
The cloud function is then deleting the userId field.
Now, since I read that it can take up to 10s for the cloud function to run, I want to make sure that the document is not queried before userId is deleted.
function isAdmin(){
return request.auth.uid == "***(admin's uid)***"
}
match /messages/{message} {
allow create: if request.auth.uid != null &&
(isNewMessage(request.resource.data)||isAdmin()) &&
userExists() && (matchesParent()||isFirstChild()||isSeed()||isAdmin());
allow read: if resource.data.userId == null || resource.data.userId == request.auth.uid
allow update: if isAdmin();
allow delete: if isAdmin();
}
The problem is the allow readline: I get FirebaseError:
Property userId is undefined on object.
Client query:
await db
.collection("messages")
.where("subcategoryId", "==", subcategorie)
.where("rank", "==", 0)
.orderBy(value, order)
.limit(paginationNumber)
.startAfter(last)
.get();
Edit: Rules are not filters and it looks like it is what I am trying to do..
Maybe the whole approach to this problem is wrong and my mistake is that I use the firebase authentication uid as document id for each user in the 'users' collection, including the admin user. So I am reluctant in having the admin's uid out there for 10s before the cloud function deletes the userId field..
What you're trying to do isn't possible because security rules are not filters. Please read that documentation carefully - your rule is apparently trying to be a filter.
Security rules can't filter out documents from a query. Either the query returns all of the matching documents, or it generates an error. The query must specify its own filters, and those filters must match what is required by the rules.
If you want to use resource.data in a rule, that can only work for individual document get(), but never for queries.
1) Instead of having your Cloud Function deleting the field, just set it to a dummy value meaning it has been removed.
2) Add a condition on the userid in your where statement, in addition to the rules
await db
.collection("messages")
.where("userid", "==", 0) // Dummy value, use whatever that will never match a real userid
.where("subcategoryId", "==", subcategorie)
.where("rank", "==", 0)
.orderBy(value, order)
.limit(paginationNumber)
.startAfter(last)
.get();
You have to use a dummy value because Firestore does not allow to filter for something that does not exist

Firestore security - Allow read if user's uid equals document's field value uid

I am trying to only allow reads to some documents if the user's uid matches the document's uid.
resource.data.uid == request.auth.uid
The security simulator gives me this errror for this line.
Error: simulator.rules line [20], column [16]. Null value error.
Is this a functionality that is just not supported by Firestore security rules?
If I understood well, I think you want something like this:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow list, create: if request.auth.uid != null;
allow get, update, delete: if request.auth.uid != null && resource.id == request.auth.token.email;
}
}
}
This code should allow users to create their own documents and access/edit only them.
Also, list allowance is required to check if the document exists before creating it, so apparently it needs to be allowed to all authenticated users.
Note that resource.id is actually the document's name. So it means that when you create a document, its name must be the user uid. And of course, you won't be able to have more than one document per collection for the same user.

Firestore - Managing User Sessions by revoking tokens

I'm looking into being able to manage user sessions - e.g. revoke tokens for bad actors and not let them to access data immediately (instead of after an hour/token lifetime).
In the guides it mentions being able to do comparisons vs stored "revoke time" in Security Rules. But it only mentions Firebase Realtime Database.
https://firebase.google.com/docs/auth/admin/manage-sessions#detect_id_token_revocation_in_database_rules
Save the refresh token revocation timestamp. This is needed to track ID token revocation via Firebase rules. This allows for efficient checks within the database.
{
"rules": {
"users": {
"$user_id": {
".read": "$user_id === auth.uid && auth.token.auth_time > (root.child('metadata').child(auth.uid).child('revokeTime').val() || 0)",
".write": "$user_id === auth.uid && auth.token.auth_time > (root.child('metadata').child(auth.uid).child('revokeTime').val() || 0)"
}
}
}
}
The key here is auth.token.auth_time
I tried to use this in Firestore Security Rules but it doesn't seem to be available.
Is this not possible in Firestore?
Maybe this works, in Firestore you have to set these rules on collections or documents.
service cloud.firestore {
match /databases/{database}/documents {
match /collection/{documentId} {
allow read, write: if request.auth.uid != null && request.auth.token.auth_time > (root.child('metadata').child(auth.uid).child('revokeTime').val() || 0);
}
}
}
I will try it myself if i find the time.
Sebe had you were correct in needing to put request before request.auth.token.auth_time.
Even though its not shown in the documentation it seems to be there.
I had to create my own document during AuthOnCreate for each user when they first sign up so that there were fields for revoke_time (the designated key I made up, you can choose your own).
Then I stored these for each user in a revoked_tokens collection and could compare against in security rules.
function notRevoked() {
return request.auth.token.auth_time > get(/databases/$(db)/documents/revoked_tokens/$(currentUser().uid)).data.revoke_time;
}
Firestore seems to be different that RTDB because you can't just use a default value of || 0. You actually need to have a value in the Firestore to compare against. I think this is because Firestore get() functions return null.
non-null rules.firestore.Resource the document, or null if it does not exist.
So if the document doesn't exist and you try to drill down into the get().data.someKey it will just error out and blow up your Security Rule. It would be like trying to do null.data.someKey

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.