this is my security rule for firebase, it throwing permission for query :
app.firestore().collection('users').where('email', '==' ,'test#gmail.com').get().then(snap => console.log(snap.docs[0].data())).catch(err => console.log("err",err));
the above query is throwing permission error!!
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;
}
}
}
why ?
Your security rule is saying that a user can only read the document in users with an id that's the same as their authenticated UID. But your query is trying to read any document where the email field has some value. Since that query could possibly try to read documents other than the one that matches the UID, the query will fail every time. With the rules you have now, the client is only allowed to get() their own specific document, with no other queries allowed.
You have to remember that security rules are not filters.
Example:
my uid: 123123
doc1:
email: test#gmail.com, id: 123123
The securtiy rule will pass for doc1.
doc2: email: test#gmail.com, id: 321321
The security rule will fail for doc2.
That is because your security rule is saying that if ALL documents have the uid in then it should pass, but because it is not, it fails.
To fix this, you would need to provide some further information on how your db is setup and what you are querying. Ie, you are finding all emails which match a criteria, or something else.
Related
I just added new value to my users collection appVersion
My user collection like:
documents/users/5kwgNgGi3sY6oCbUAg9v
so i just added value versionCode to collection. if user download new app it will be update
My rule now:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth !=null
}
}
}
what i need as a if else statement
if(request.auth!=null){
firebase().collection('users').where('UID','==',request.auth.uid).get().then(x=>{
if(x.docs[0].versionCode==4){
allow
}
else{
deny
}})
else{
deny
}
There is no way to perform a query in your security rules, as that would never scale on the backend.
Instead, what you'll want to do is store the user documents with the UID as the document ID, so that the document you show is at path documents/users/xHzKoXbrZqbHZY8DI3wkXnMShF92. Then you can use get() in your rules to load the document, as shown in the documentation on accesing other documents in your rules.
I'd also recommend checking out the documentation on attribute-based and Role-based access, as it covers a pretty similar scenario.
My database structure is:
users
david#gmail.com
records
ApK2DFpG87NDGYutgAVO
pulse: 80
Bryd87NAS20dfDGYtghg
pulse: 78
eva#fb.com
records
A81hxASDKH38dhaj9321
pulse: 93
A82ndasklih38ASD2eda
pulse: 67
and rules are:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{email} {
allow create, read, update, delete: if request.auth.token.email == email;
}
}
}
I would like every user (e.g. foo#gmail.com) to be able to read and write data only under that user (users/foo#gmail.com/**).
When I read users/me#gmail.com in the Rules playground (while being authenticated as me#gmail.com), I get "Simulated read allowed", as expected.
However, when I read users/me#gmail.com/records from my app (while being authenticated as me#gmail.com), I get:
FirebaseError: Missing or insufficient permissions.
What am I missing?
By the way, why the Rules playground doesn't allow reading collections (e.g. users/me#gmail.com/records)? It says:
Path must be document-level
You should take advantage of the recursive wildcards of version 2 of the security rules, as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{email}/{document=**} {
allow create, read, update, delete: if request.auth.token.email == email;
}
}
}
As explained in the doc, it will match documents in any subcollections of the users collection as well as documents in the users collection.
By the way, why the Rules playground doesn't allow reading collections
If I am not mistaking, this is because rules are not filters, and therefore you need to exactly specify the document you want to target in the "Rules Playground".
In a firestore database, I am using email only as authentication. The database has the following structure:
companies
jobs
users
For sake of readability (and for customer's peace of mind), I am using the email address as the Document ID for the users collection. A user document looks like this:
document id: tbogard#gmail.com
fields
name_first: Terry
name_last: Bogard
jobs_read: ["job_A"]
jobs_readwrite: ["job_B, job_C"]
When I try to grab the request token in the rules, it gives me errors (search for ***):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function userExists(){
// *** Function not found error: Name: [exists]. ***
return exists(/databases/$(database)/documents/users/$(request.auth.token.email));
}
function userData(){
// *** Function [get] called with malformed path: /databases/(default)/documents/users/ ***
return get(/databases/$(database)/documents/users/$(request.auth.token.email)).data;
}
// For now, let's keep it simple, and enforce the User read/readwrite rules on Jobs.
match /jobs/{jobId}{
allow read: if userExists() && (jobId in userData().jobs_read || jobId in userData().jobs_readwrite);
allow write: if userExists() && jobId in userData().jobs_readwrite;
}
// Only allow Users to see their own profile and companies they belong to.
match /companies/{companyId}{
allow read: if userExists() && userData().email in resource.data.employees;
allow write: if false;
}
match /users/{userId}{
allow read: if userExists() && userData().email == resource.data.email;
allow write: if false;
}
}
}
I'm guessing request.auth.token.email is returning something like an optional? I can't find anything in the documentation explaining how the functions get/exists, which require a path, handle this. Is there a way that I could make the Firestore UID for each user the email address instead of the random string, or can I fix these rules some way?
So I'm writing rules for my database in firebase. And I'm trying to allow a person to delete his own post only. But when I'm testing it anybody can delete anyone's post.
I have tried writing if request.auth.uid==resource.data.User.uid but it doesn't work
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId}{
allow delete: if request.auth.uid==resource.data.User.uid
allow read;
allow write,update: if request.auth.uid!=null;
}
}
}
I tried simplifying it further by changing it to
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId}{
allow delete: if request.auth.uid==1
allow read;
allow write,update: if request.auth.uid!=null;
}
}
}
But still anyone can delete anybody's post. And obviously no one has uid as 1.
Here is my post collection
Content "Hello"
Title "Hey"
User
displayName "Vikram Singh Bedi"
email "vikrambedi_bt2k16#dtu.ac.in"
uid "dCQvboYvffQ1kOqkMqgs2txNKvo1"
comments 0
createdAt 8 September 2019 at 14:10:31 UTC+5:30
favorites 0
I want only the person who created the post to be able to delete it.
Here is the code to delete a post
<button
className="delete"
onClick={() => {
firestore.doc(`posts/${id}`).delete();
}}
>
How do you send a delete request?
For example if you are using Admin SDK to delete, then Admin SDK will bypass the rules.
When you grant permission for a write operation, that grants permissions to all of create, update and delete operations.
You're combining a granular write rule with non-granular delete and update rules, which I suspect means the non-granular write rule wins out (since they're just OR'ed together).
If that is the case, use only non-granular write rules:
allow delete: if request.auth.uid==resource.data.User.uid
allow read;
allow create,update: if request.auth.uid!=null;
Writing rules for Firestore it seems that custom variables are not working.
Did anyone know why or have seen similar behaviour?
Using the below I got access denied although the uid is in the array of admin.
service cloud.firestore {
match /databases/{database}/documents {
match /conferences/{confid} {
allow read,write: if request.auth.uid in get(/databases/$(database)/documents/conferences/$(confid)).data.admin;
}
}
}
Simulator is giving the below error:
Function [get] called with path to nonexistent resource: /databases/%28default%29/documents/conferences/%7Bconfid%7D
Also testing this on a real devices I got access denied.
If however I use the ID of the document like below it works and access is granted.
service cloud.firestore {
match /databases/{database}/documents {
match /conferences/{confid} {
allow read,write: if request.auth.uid in get(/databases/$(database)/documents/conferences/ySWLb8NSTj9sur6n2CbS).data.admin;
}
}
}
Obviously I can't hardcode this for each and every ID.
UPDATE
Apart from logging the case with support I have done some further testing.
On the below the simulator is now granting access.
service cloud.firestore {
match /databases/{database}/documents {
match /conferences/{confID}{
allow read, write: if request.auth.uid in get(/databases/$(database)/documents/conferences/$(confID)/permissions/permission).data.users;
}
}
}
For reference I use the below to query from my web-application:
db.collection("conferences")
.get()
.then(query => {
console.log("SUCCESS!!!")
query.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
}).catch((e) => {
console.log(e)
})
This is the log from the browser:
FirebaseError: Missing or insufficient permissions.
at new FirestoreError (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:352:28)
at JsonProtoSerializer.fromRpcStatus (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:5649:16)
at JsonProtoSerializer.fromWatchChange (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:6146:44)
at PersistentListenStream.onMessage (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:14350:43)
at eval (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:14279:30)
at eval (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:14319:28)
at eval (webpack-internal:///./node_modules/#firebase/firestore/dist/index.cjs.js:7411:20)
I am using the latest Firebase package 5.8.3.
If I change the above rule to something simple like below it got access as long as I am logged in with a user:
service cloud.firestore {
match /databases/{database}/documents {
match /conferences/{confID}{
allow read, write: if request.auth.uid != null
}
}
}
This even confuses me more. Is this because the rule is more complex and it takes too long to get this verified and gives back access denied?
Update-2
Quickly tested this in a mobile app via Flutter. Same result. Access denied with this ruleset.
service cloud.firestore {
match /databases/{database}/documents {
match /conferences/{confID}{
allow read, write: if request.auth.uid in get(/databases/$(database)/documents/conferences/$(confID)/permissions/permission).data.users;
}
}
}
I think my problem was the query's don't match security rules. If you would only access a single specific document it would work but if you query multiple documents in a collection you got blocked by the security rules.
I had two options. Restructure my data so that a single document will hold all the data I need or redesign security rules to match query's.
In the end I have attached to each document an indentifier like the UID to make sure query's match the security rules.
One solution would be to put the users with permissions into an array in the conference document instead,
so request.resource.data.permissions
So, instead of this:
get(/databases/$(database)/documents/conferences/$(confID)/permissions/permission).data.users
use this:
request.resource.data.permissions
This wouldn't solve the get() problem, but it would eliminate the need for a get() call, which could save you 15% or more on your quota.