I am starting to restrict the access to my Firebase Database. So far I have three collections where I simply want to return true for testing purposes.
I did it like this
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /devices/{device} {
allow read, write: if true;
}
match /users/{user} {
allow read, write: if true;
}
match /groups/{group} {
allow read, write: if true;
}
}
}
When I try to test this, I can't access the data no matter what. It gets always denied.
Your rules are correct, I see the issue is how you are using the playground,
In the textbox just enter something like
/devices/yourdeviceId
When you use the console simulator to test your rules, the field for the document to test should only contain the path of the document to get, using only the names of specific collections and document IDs. If you're trying to test document "foo" in collection "devices", then your path should be "/devices/foo". Don't enter the full match path, and don't use any wildcards - you should refere to an actual document in the same way that you would refer to it in your app code.
Related
I've got a top-level collection of documents, each of which has an array field containing the email addresses of the users that are permitted to view the document in question (there's a reason in this case that it's the email addresses, not the UIDs).
Additionally, I have a custom claim set up on some users that mark them as admin who should have read/write access to everything.
The following security rule works well for the top level:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
function allowRead() {
return resource.data.users.hasAny([request.auth.token.email]);
}
allow read: if allowRead();
allow read, write: if request.auth.token.admin == true;
}
}
}
I want the rule to check the 'users' array field when requesting documents in a collectionGroup query across nested collections called 'projects' and block access if a nested document is requested for which there is no match to the 'users' array field in the parent.
I get that the allowRead() funtion won't work as is, even if I add a recursive wildcard after match /accounts/{account} like /accounts/{account}/{document=**} since the definition of resource.data will be different at that level and won't return true for the allowRead() function.
I know that Firestore rules don't really support an operation like 'check foo in parent' when querying, so I was hoping for some advice on the best way to approach this requirement.
The collectionGroup query to the nested collections is
this.firestore.collectionGroup('projects', (ref) => ref.where('accountId','in',accountIds)).get()
in which 'accountIds' is a given array of the ids of the parent account, which is stored in each document in the projects collection for ease of querying with collectionGroup.
I don't really want to move the nested collection to top level because then I'd have to store the users array in each document as well as the reference to the account to which it belongs, but perhaps that's the best way to do this?
Or am I thinking about this whole thing wrong? I'm fairly new to Firebase.
I've solved this with the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /accounts/{account} {
function allowRead() {
return resource.data.users.hasAny([request.auth.token.email]);
}
allow read,write: if request.auth.token.admin == true;
allow read: if allowRead();
}
match /{path=**}/projects/{project} {
allow read,write: if request.auth.token.admin == true;
allow read: if get(/databases/$(database)/documents/accounts/$(resource.data.accountId)).data.users.hasAny([request.auth.token.email]);
}
}
}
The only issue is that as far as I can tell, this is going to double the amount of billable queries because there's an extra 'get' made any time the nested collection is queried.
Any thoughts from the community on better ways to do this would be welcome!
for example in Firstore i have collection called products and every doc has boolen field called isAllow : false
now in Security Firstore Rules How to make users can read only the docs with true value of isAllow field and the same with write .. i read the documentation but couldn't understand exactly what i want
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
As Dharmaraj answered, you can allow only reading of those documents in security rules with:
match /products/{product} {
allow read: if resource.data.isAllow == true;
}
But keep in mind that security rules are not filters on their own, and instead merely ensure the client doesn't try to read data it isn't permitted to. To meet the security rule above, you'll also need to use a query to read the allowed data:
firebase.firestore()
.collection("products")
.where("isAllow", "==", true)
Yes,
match /collection/{document} {
allow read: if resource.data.isAllow == true;
}
Here resource.data is a map of all of the fields and values stored in the document and the above rule will allow read operation only when isAllow field in the document being accessed is true.
I am new to firestore and its security rules so apologies if this sounds stupid.
I have 3 collections, data, roles and trainingRequests. In roles I am storing users id along with their name and role as shown in the image attached. What I wanted to do was to give access of read and write only if the role of the user is "external". So, for that purpose the rule I wrote is as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /trainingRequests/{tId} {
allow read, write: if get(/databases/$(database)/documents/roles).data[request.auth.uid].role == "external"
}
}
}
But this is not working at all. So, I wanted to know what I am doing wrong and what's the right way to write the rule for this purpose.
I know that custom claims is a better approach but I still want to try this out.
You need to pass a document path to the get() method.
With
get(/databases/$(database)/documents/roles)
you are actually passing the roles collection path.
The following should do the trick:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /trainingRequests/{tId} {
allow read, write: if get(/databases/$(database)/documents/roles/$(request.auth.uid)).data.role == "external"
}
}
}
The get() method returns a rules.firestore.Resource on which you call the data property which returns a map of the document data.
You'll find more details and examples in the doc.
In Firebase Firestore, I'm trying to allow only (custom-assigned) admins to write/update/delete resources, and for that I've got these security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /resources {
allow read;
allow write, update, delete: if get(/users/$(request.auth.uid).isAdmin);
}
match /resources/{resource} {
allow read;
allow write, update, delete: if get(/users/$(request.auth.uid).isAdmin);
}
}
}
I'm signing in with the user that is marked as an admin in the users collection:
NfwIQAjfNdS85yDvd5yPVDyMTUj2 is the UID gotten from the Authentication pane:
However, for some reason (UPDATE: reasons identified; see answer), I'm getting PERMISSION_DENIED errors when writing to the resources collection after being absolutely sure I'm signed in with the admin user.
Perhaps it is possible to view request logs from Firestore? Then I could have a look at what request.auth.uid looks like to match it up with my collections and rules.
While writing my question, I made it work! I made two mistakes, both of which could have been avoided if I read the docs properly.
Firstly, all calls to the service-defined function get needs to prefix the path with /databases/$(database)/documents/. So that this rule:
allow write: if get(/users/$(request.auth.uid)).isAdmin;
becomes this:
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).isAdmin;
It's long, I know, but that's how it is. I'm not sure why Firestore isn't able to do that by itself, though, seeing as that same path prefix will stay the same across all calls to get, but perhaps this is for some future feature that isn't ready yet, like cross-database querying or something.
Second, the get function will return a resource, which in turn you'll need to call .data on to get the actual data that it contains. Thus, instead of doing this:
get(/path/to/user/).isAdmin
you'll need to do this:
get(/path/to/user/).data.isAdmin
Now I just wish I was able to extract that logic into a user-defined function:
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;
}
But doing so results in a PERMISSION_DENIED again, and without knowing what's actually going on in the function, I'm not sure if I'll spend more time trying to figure this out now.
UPDATE: #Hareesh pointed out that functions must be defined within the scope of a matcher, so it's possible to put the function in the default top-level matcher like this:
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin == true;
}
// ...
}
}
Some points i noticed
match /resources is pointing to a collection, that rules has no effect on its documents. here i am quoting from the doc
Rules for collections don't apply to documents within that collection. It's unusual (and probably an error) to have a security rule that is written at the collection level instead of the document level.
so you don't have to write rules for collections
Then in the rules allow write, update, delete: you can say either allow write: or specifically allow create, update, delete: any of the three options or combine them.
try this
service cloud.firestore {
match /databases/{database}/documents {
match /resources/{resource} {
function isAdmin() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).isAdmin ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;
}
allow read;
allow create, update, delete: if isAdmin();
}
}
}
I have a data structure like this (Collections and Documents rather than JSON of course but you get the idea):
{
users: {
user1:{
name: Alice,
groups: {
groupA:{subbed:true},
groupB:{subbed:true}
}
},
user2:{
name: Bob,
groups: {
groupC:{subbed:true},
groupD:{subbed:true}
}
}
}
}
Basically this is registered users IDs and the group IDs that each user is subscribed to. I wanted to write a security rule allowing access to a users profile and sub-collections only if they are the current auth user and, based on my reading of the docs, I thought that a wildcard would achieve this...
match /users/{user=**}{
allow read,write: if user == request.auth.uid;
}
With this in place I can read the user document fine but I get a permissions error when I try and read the groups sub-collection. I can only make it work by matching the sub-collection explicitly...
match /appUsers/{user}{
allow read,write: if user == request.auth.uid;
match /groups/{group}{
allow read,write: if user == request.auth.uid;
}
}
...so my question is, what is the difference between the two examples and what am I misunderstanding about the recursive wildcards? I thought that the {user=**} part of the first example should grant access to the user document and all its sub-collections, sub-sub-collections etc etc ad infinitum (for the authorised user) and should remove the need to write rules specifically for data stored lower down as I have had to do in the second example.
I've only been messing around with Firestore for a short time so this could be a real dumb question :)
Thanks all
The firebase docs are a bit confusing when it comes to using the recursive while card. What I found in testing was that I needed to set two rules to give a user permission to write to the users document and all sub collections (and their sub documents) which is the most logical setup for managing user data.
You must set two rules.
Give user permission to the /users/{userId} document
Give user permission to all sub collections and their sub documents that begin at the /users/{userId} path.
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
}
match /users/{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
Rules
Sorry about including the images. I couldn't get SO to format them correctly.
I think the problem is that, while you are indeed using the subcollections wildcard =**, you are then allowing permissions only if user == request.auth.uid, so this is what happens (pseudocode):
(when accessing users/aHt3vGtyggD5fgGHJ)
user = 'aHt3vGtyggD5fgGHJ'
user == request.auth.uid? Yes
allow access
(when accessing users/aHt3vGtyggD5fgGHJ/groups/h1s5GDS53)
user = 'aHt3vGtyggD5fgGHJ/groups/h1s5GDS53'
user == request.auth.uid? No
deny access
You have two options: either you do as you've done and explicitly match the subcollection, or use this:
function checkAuthorization(usr) {
return usr.split('/')[0] == request.auth.uid;
}
match /users/{user=**}{
allow read,write: if checkAuthorization(user);
}
(the function must be inside your match /databases/{database}/documents, like your rule)
Let me know if this works :)
Security rules now has version 2.
match/cities/{city}/{document=**} matches documents in any
subcollections as well as documents in the cities collection.
You must opt-in to version 2 by adding rules_version = '2'; at the top
of your security rules.
Recursive wildcards (version 2).
This is what works for me:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}