When I run a Firebase/Firestore query in app or simulator with a Security Rule using request.response it consistently fails the check. The simulator throws an "Error: simulator.rules line [XX], column [XX]. Property resource is undefined on object."
That would imply the document I'm querying isn't real. It exists in my db, and just in case the simulator doesn't check real documents, I'm making sure to do a sim "create" call for the document immediately before running the get. No actual document ID that I insert seems to be found in this collection or any other for that matter. Clearly overlooking something vital and probably absurdly basic:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /responses/{id} {
allow read:
if request.auth.uid == request.resource.data.userID;
}
}
FYI The query is authenticated, and I'm hitting the path "responses/documentName" which does in fact exist with a "userID" property. Rules that don't use the request.response are working fine.
This is clearly a super boilerplate rule/query. What am I overlooking?
Thanks so much :)
As the error message says, request.resource does not exist. It only exists on writes
Try this instead:
if request.auth.uid == resource.data.userID;
Related
Alright so I was creating an application that works with firebase and when a user signs in, user's ID is stored in the data base. But when I was creating this I ran into an error that would pop up after a user signs in FirebaseError: Missing or insufficient permissions..
I did some research. It was the firestore rules, which were set to default as
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
I changed this as
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
That's right from false to true. Is the right way to do it? Help would be appreciated.
If you are just testing and no one is going to use it it's fine but please, keep in mind that this kind of ruleset allows everyone to read and write to each of your files under the documents collection.
I ran into this kind of problem too while starting out with firebase and I know it's overwhelming but I would suggest you try and implement a ruleset more secure following this get started guide by google.
Let me know if you need any further information.
I am developing an application, which uses Firestore as a database. I have a collection of admins, where the id of the documents is the email address of the admin. I want to create a security rule, which enables only admins to create new documents. My current solution looks like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{collectionName}/{document=**} {
allow create: if exists(/databases/$(database)/documents/admins/$(request.auth.email));
}
}
}
But when I try to run the admin app, it gives a missing or insufficient permissions error. Furthermore, when I try to test it in the rules playground, it gives the following error:
Error running simulation — Error: simulator.rules line [6], column [24]. Function not found error: Name: [exists].; Error: Invalid argument provided to call. Function: [exists], Argument: ["||invalid_argument||"]
As far as I understand, somehow the exists function is missing and the document id is invalid, but why? It's just a string, isn't it?
If you are trying to get the email associated with the auth request, you have to do it like this:
$(request.auth.token.email).
You can see details on the structure of the Request.auth object here.
There is no option to make a user Admin and give special privileges in realtime database I think it goes the same to the FireStore.
But what you can do is add a field in the user like userType and give it the value Admin whenever an admin Signs up, subsequently you can create rules based on that.
I have only one match statement and it is set to pass if the collection or document path is in the the user's permissions.read array. I'm only testing user u1 which has a permissions.read property.
match /{document=**} {
allow read: if document in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.permissions.read;
}
I get the following responses when requesting the collection /companies/c1/departments/d1/employee or the document /companies/c1/departments/d1/employees/e1
Success when tested in the Firestore rules playground
Fails but returns correct response when tested locally using Flutter app on android emulator
Fails and no results using Postman and Firestore REST API
This may have something to do with the way I'm using the get method since the match itself is not dependent on a where clause in the request or data contained in the resource itself. However, it seems that the data I've provided the match should be enough to determine if a single document or all documents in a collection requested will pass or fail since it is based solely on the resource requested and the permissions.read property for the requesting user.
Any help would be appreciated.
Here's more info:
My database has the following collections and documents:
users/u1
companies/c1/departments/d1/employees/(e1, e2)
User u1 has the following property:
{
permissions: {
read: [
'companies/c1/departments/d1/employees',
'/companies/c1/departments/d1/employees',
'companies/c1/departments/d1/employees/e1',
'/companies/c1/departments/d1/employees/e1'
]
}
}
Here's the Playground result:
Playground Result
Here's the Flutter request which is returning the correct data:
Firestore.instance
.collection('companies/c1/departments/d1/employees')
.document('e1')
.snapshots()
And the error:
W/Firestore(28552): (21.3.0) [Firestore]: Listen for Query(companies/c1/departments/d1/employees/e1) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
Here's the Postman request which does not return any data:
https://firestore.googleapis.com/v1beta1/projects/my-project/databases/(default)/documents/companies/c1/departments/d1/employees/e1
And the error:
{
"error": {
"code": 403,
"message": "Missing or insufficient permissions.",
"status": "PERMISSION_DENIED"
}
}
The errors seem to be the same.
It's also worth mentioning that authentication is working and simply changing the match statement to the following works fine on both Flutter and Postman with no errors:
match /{document=**} {
allow read, write: if request.auth != null;
}
SOLVED
You can not compare a path (document) to a string
(values in permissions.read) which I'm doing here:
allow read: if document in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.permissions.read;
FYI, while you can convert a string to a path, currently, there is no way to convert a path to a string. Even request.path is of type path so no luck there. If you want to compare the resource path to a value stored in a map or array you can store the path as a reference. However there is a limitation, you can only store references to documents not collections.
Normally I test my security rules for Firestore with the Firestore emulator. Until a few weeks ago, everything worked wonderfully. I changed nothing and now the Firestore emulator is freaking out. I can't use request and resources anymore.
My Rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /Test/{testId} {
allow read, write: if isLoggedIn();
}
function isLoggedIn() {
return request.auth.uid != null
}
}
}
If i visit http://localhost:8080/emulator/v1/projects/projektName:ruleCoverage.html, I get this message: Expression never evaluated
Error Screenshot
firebase --version: 8.2.0
Any ideas to fix this Firestore emulator error?
Firestore docs are a bit confusing. They state that the URL should be:
http://localhost:8080/emulator/v1/projects/<database_name>:ruleCoverage.html
However, they don't explain what <database_name> should be replaced with your project_id. Yes... reading the URL some might thing is obvious, but is really the argument name that should make it obvious. Plus, in Firestore databases are not created neither accessed by name.
SOLUTION:
<database_name> should be replaced by your project_id (this value is available in the files .firebaserc or google-services.json
So, if your project_id is my-amazing-app, your rulesCoverage url would be:
http://localhost:8080/emulator/v1/projects/my-amazing-app:ruleCoverage.html
I want to allow links inside my application looking like:
mywebsite.com?u=nc27ri3ucfyinyh3
where nc27ri3ucfyinyh3 is a uuid, so the link can be sent to an anonymous user. The anonymous user should be able to view the page (database read), but it should also log to the database that they've viewed that link (database write).
As we get a warning when our firestore rules look like
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
as it is not secure.
Your security rules are defined as public, so anyone can steal, modify or delete data in your database
How should we handle the case of these anonymous users?
The first thing is that you could write a more restrictive set of rules than you have there (for example, restrict writes to just one collection (by changing the match /{document=**} line to something more restrictive (e.g. just the links collection or something). This, of course, still effectively allows anonymous users the full run of your database, but only within that collection.
Additionally, you can add validation to the incoming request via the request.resource object) -- its likely due to the anonymous nature of the user that you will still have a relatively insecure set of rules.
The data validation approach can look at both the current state of the database (in resource.data) as well as the contents of the incoming request (in request.resource). Here is the reference documentation for Resource and Request objects.
Here is an example rule that assumes these documents:
Exist in the /uuids collection
Are created by some other method (authenticated user, admin API, etc)
Only need to be fetched by ID, not queried as a set.
Only have 2 fields: content and visits
visits must be an integer, and is only allowed to be incremented
When the document is created, visits is initialized to zero.
I have not extensively tested these rules, only used the simulator to confirm they behave roughly as expected, I recommend you write extensive tests for any rules you intend to deploy. In particular, I am not certain about the behavior of the test for only being incremented when the document is under heavy contention.
rules_version = '2';
function notUpdating(field) {
return !(field in request.resource.data)
|| resource.data[field] == request.resource.data[field]
}
service cloud.firestore {
match /databases/{database}/documents {
match /uuids/{uuidValue} {
allow get: if true;
allow update: if (request.resource.data.keys().size() == 2 &&
notUpdating('content') &&
request.resource.data['visits'] == int(request.resource.data['visits']) &&
request.resource.data['visits'] > 0 &&
request.resource.data['visits'] == resource.data['visits'] + 1);
allow write: if false; // these 4 lines can also just be omitted
allow list: if false;
allow delete: if false;
allow create: if false;
}
}
}
This would allow you, for example, to ensure that only exactly the field you want is being touched, and only with valid data (e.g. positive integers or similar).
Remember -- the security rules are your only protection -- users can run arbitrary code against the database within those rules, not just code that you have given them. So, for example, if they can blanket read the collection, they can literally read the entire set of documents in that collection.
Alternatively, it might instead make sense to write an http, https, or callable cloud function that does exactly what you need -- register that the link has been used via a write, and then redirect or serve the necessary data itself. This gives you a lot more control over the specific write, but it does come with some added cost. The advantage here is that you wouldn't need to allow any public or open access to the database at all.
Cloud functions can also be served off of mywebsite.com if that web site is hosted on Firebase Hosting, via rewrite rules.