I am having difficulty trying to diagnose a particular rule in my firestore.rules file. See that question here for context.
Is there a way to debug the firestore.rules file and/or functions? I'm using unit testing and the emulators to test my rules, but I would really love to see exactly what values are being evaluated by the rules engine.
For instance, here is my firestore.rules file:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin();
}
}
}
}
}
function isAdmin() {
// removed for security
}
function belongsToCurrentClass() {
// retuns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
What I'd love to do is set breakpoints or step through the code. When attempting CRUD operations on a organizations/{orgId}/classes/{classId}/students/{studentId} path I'd love to inspect exactly what values the orgId, classId, and studentId variables are holding, as well as the resource and request parameters. I'd love to inspect exactly which document (if any) is returned by the get request in belongsToCurrentClass and what the return value is.
Does anyone know of any way to do this? I think I'd answer my question referred to above in 10 seconds if I could just see the data being evaluated.
There is a local emulator for Cloud Firestore security rules. This is your best (and really only) tool for digging into security rule execution. There is no step-through debugging, but you can see a lot of debug output in the console.
https://firebase.google.com/docs/rules/emulator-setup
We can add the built-in debug function to rules. As noted in a comment, you'll see an unhelpful message like this in the browser:
Received: [path] Expected: [bool]. for 'list' # L6
On the plus side, we won't forget to remove debug messages. Tail the log file to see the output: tail -f firestore-debug.log
For example, to see which paths are being called:
match /databases/{database}/documents {
match /{document=**} {
allow create, read, update, delete: if debug(request.path);
}
}
Related
This is a follow up on this post: Firebase Firestore security rules - read all nested docs based on resource (but it's null)
I'm having a hard time figuring how Firestore works!
So let's say I have this Bases collections:
{
name: "Base 1",
roles : {
user_1: true
}
},
{
name: "Base 2",
roles : {
user_2: true
}
}
I want to fetch all Base where user_1 is in resource.roles. I do :
db.collection('bases').where('roles.user_1', '==', true).get()
And it works great. All server side so no one can try to alter the filter: All good.
But, I want to enforce this filter with a security rule in firestore. Just to be sure! So I've wrote:
function hasRole(){
return request.auth.uid in resource.data.roles
}
match /bases/{document=**}{
allow read: if true; // signedIn() && hasRole();
}
match /bases/{baseId}{
// allow read: if signedIn() && hasRole();
allow update, delete: if false && signedIn()
allow create: if signedIn()
}
And it works when I try to access a specific resource in the rule simulator but now my .get() method receive a not-enough permission. Funny enough, When I uncomment the read into /bases/{baseId} it works perfectly (if I comment the /bases/{document=**} of course).
So I can make it work but I'd still like to understand why the {document=**} rule work in simulator and not when I'm trying to fetch from a server.
Thanks a lot for your help :)
P.S: I didn't find a way to simulate a nodejs .get() in the simulator which is not super convenient to learn this stuff. Any resource on that will be read and helpful.
After watching movie and read i really don't understand what i want to do.
I'm explain, i have a website, he read information in my database realtime and printed on my website ...
And i got a system for vote, visitor can coming and click on button for send a vote and i sended directly on my server database.
My problem is actually write/read is true and all word but if i pass anyone on false one of two don't work.
But what need to do for nobody can modifi or delete my data ??
The rules is clear i can for who i want (admin,guest,anonymous..) what i want. But is not my problem, i don't understand what i want to do :/
Thanks a lot !
From what you have explained in your comment I think you want to make the firebase rules read-only?
If this is true the following code will accomplish that:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if false;
}
}
}
Otherwise, you could allow writing tp database only if a user is authenticated (signed in):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth.uid != null;
}
}
}
more information on firestore rules can be found here: https://firebase.google.com/docs/firestore/security/overview?authuser=0
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.
I posted a question about this yesterday but I'm creating a new one with more details.
Firestore .setData is blocked by update rule not create
I've run the simulator and the rules work there. Also when I create the document and change setData in the swift code to update the code works. It appears to only fail when creating the document. But the catch is that when I remove the update rule or simply change it to allow update: if false; the setData (or seen as create by the rules) executes properly. I have no clue whats going on nor do I know of any tools for getting a better insight.
match /users_real/{userID} {
allow create: if true;
allow read: if isOwner(userID);
allow update: if (request.writeFields.size() == 1);
}
set data:
self.docRef.collection("users_real").document("adfadsf").setData(post) { (error) in
if let error = error {
print("He dead!: \(error.localizedDescription)")
}
else {
print("it worked, for now")
}
}
Firebase Support confirms that there is a bug related to the evaluation of request.writeFields.size(). No estimate was given of when it will be fixed.
The existence of the bug can be demonstrated with the following rules:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// This should always evaluate to true, but does not.
allow create: if (request.writeFields.size() == 1) || (request.writeFields.size() != 1);
allow update: if true;
}
}
}
Although the create rule should always evaluate to true, an attempt to create a city fails with Permission Denied. It seems that the problem with request.writeFields affects not only the rule in which it appears, but also other rules for the path. For the rules shown above, an attempt to update an existing city also fails with Permission Denied.
In firebase firestore security rules, I want to allow a request only if a particular document does not exist. My code is:
match /users/{user_id} {
allow create: if !exists(/databases/$(database)/merchants/$(request.auth.uid));
}
I am pretty sure the document does not exist but it does not work.
Both exists() and !exists() give false somehow, or maybe !exists() raises some error.
I have even tried:
match /users/{user_id} {
allow create: if exists(/databases/$(database)/merchants/$(request.auth.uid)) == false;
}
Is there any way to make this work?
Are your rules inside the database block? Cause I had a similar problem and was actually putting the rule bellow the database block, and my rule was using the $(database)parameter just like yours
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user_id} {
allow create: if !exists(/databases/$(database)/merchants/$(request.auth.uid));
}
}
}