Firebase database rules only two out of three conditions work - firebase

I have "UID" database (uid root for each user).
I try to share "uid" roots between users, and i want to give read access to "UID" root in one of three cases:
1. The user who access is the owner.
2. The user who access is located in "UID\PERMITTED_USERS" of target root.
3. The user who access is located in "UID\TEMP_USERS" of target root.
To accomplish this I created next rule:
".read" : "$uid === auth.uid || (root.child(root.child(auth.uid).child('PRE_SHARE').val()).child('TEMP_USERS').hasChild(root.child(auth.uid).child('TEMP_PERMIT').val()) || root.child(root.child(auth.uid).child('CURRENT_SHARE').val()).child('PERMITTED_USERS').hasChild(auth.uid))"
But I was disapointed to discover that only the first two conditions are checked, and the third is not. (I changed the order of the conditions and every time I could access using the first two in a row).
Is there a way to solve this?
EDIT:
Adding db example:

So after lots of tests I found the problem. My code deletes PRE_SHARE as well as TEMP_USERS, and when rule tries to access val() of non existent PRE_SHARE it gets null pointer exception. Too bad Firebase doesn't write this exception, it would save me lots of time...
".read" : "$uid === auth.uid ||
(root.child(auth.uid).hasChild('PRE_SHARE') &&
root.child(root.child(auth.uid).child('PRE_SHARE').val()).hasChild('TEMP_USERS') &&
root.child(auth.uid).hasChild('TEMP_PERMIT') &&
root.child(root.child(auth.uid).child('PRE_SHARE').val()).child('TEMP_USERS').hasChild(root.child(auth.uid).child('TEMP_PERMIT').val())) ||
(root.child(auth.uid).hasChild('CURRENT_SHARE') &&
root.child(root.child(auth.uid).child('CURRENT_SHARE').val()).hasChild('PERMITTED_USERS') &&
root.child(root.child(auth.uid).child('CURRENT_SHARE').val()).child('PERMITTED_USERS').hasChild(auth.uid))"

Related

Firestore Security Rule: Check user input on get query

I'm creating a blog with Firestore. I have two collections called users and blogPosts. Each document in blogPosts contains name, createdAt, createdBy and password (plain string) field.
I want to create a security rule so clients can access a document only if they provide the correct document password.
According to an idea in this link, I wrote a rule like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /blogPosts/{postUid} {
allow write: if
request.resource.data.createdBy == request.auth.uid &&
request.resource.data.name is string &&
request.resource.data.name.size() > 2 &&
request.resource.data.name.size() < 32 &&
request.resource.data.password is string &&
request.resource.data.password.size() > 5 &&
request.resource.data.password.size() < 32
allow read: if
request.auth != null &&
request.resource.data.password == resource.data.password // <---- THIS LINE IS NOT WORKING
}
}
}
I get this error in playground with the rule above: Error: simulator.rules line [16], column [8]. Property resource is undefined on object. So it means we don't have resource.data on read queries.
How can I achieve my goal with Firebase security rules, so only clients that has blogPosts password can access to documents?
What you're trying to do isn't possible with security rules (and also isn't really "secure" at all). A client app can't simply pass along some password in a query. The only time input is checked is for document fields in a write operation, not document reads.
If you want to check a password, you will have to make some sort of API endpoint and require that the caller provide the password to that endpoint. Again, bear in mind that this is only as secure as your ability to keep that password a secret, because once it becomes known (perhaps by simply reverse engineering your app), anyone will be able to use it.

How to hide specific node in firebase realtime database

I have 2 questions related to firebase realtime database.
Q1) Is it possible to hide a certain node from public and only one person have access to that node ?
( I want to save a transaction key in database and only 1-2 persons can access that key from database. )
Q2) If my security rules for database are set to public, can anyone download/access complete data or they must know the structure in order to access data from database.
A1/ Yes it is possible to limit access to a certain node to a only subset of users. There are several approaches for that. If it is a small and more or less stable subset of users, you can check if their uid is contained in a specific node listing the admin users uids, as shown below:
{
"rules": {
"secretnode": {
//only an admin user can read
".read": "auth != null && root.child('adminusers/' + auth.uid).exists()"
},
"adminusers": {
".read": false,
".write": false
},
"othernodes": {
".read": true,
".write": true
}
}
}
And, for example, in the database you save the admin uids as follows:
DBRoot
- adminusers
- uid1
- name: "aaaaaa"
- otherDataItem: "xxxxxx"
- uid2
- name: "bbbbb"
- otherDataItem: "yyyy"
A2/ Yes, if your security rules for database root are set to public, "anyone (can) download/access complete data". Note that, as explained in the documentation:
Shallower security rules override rules at deeper paths. Child rules
can only grant additional privileges to what parent nodes have already
declared. They cannot revoke a read or write privilege.
In other words, this means that if your security rules for the database root are set to public anyone can download the full JSON tree representing your data, without the need to know its structure.

Firebase security rules - writing from Firebase functions

I'm trying to implement proper Firebase security rules and ran into one challenging issue. I've got public listings which should be viewable by all, can't be deleted or edited by anyone except by the author of the listing. When someone buys (via PayPal plugin) a given listing, I need to write to this listing changing it's status to SOLD. Essentially this is like a third party (not the listing author) has to write to a given listing.
Here are the rules I have so far:
"listings": {
".read": "auth.provider === 'facebook'",
"$listingID": {
".write": "auth.provider === 'facebook' && ((!data.exists() && newData.exists()) || root.child('listings/'+$listingID+'/UID').val() === auth.uid)"
}
}
I have a feeling that I need to write to Firebase from my backend (Firebase functions), but I'm not sure how the security rules will work out, as it would still be the buyer (3rd party) upon whose request we'd need to write the new status SOLD to the listing node which is only editable by the author.
Any advice/recommendations on how to go about this situation will be highly appreciated!
Many thanks!

Firebase database rules – `data.exists()` always seems to be true, possible bug?

I am trying to secure my firebase database to allow the creation of new records, but not allow the deletion of existing records. Ultimately, I plan to utilise Firebase authentication in my app as well, and allow users to update existing records if they are the author, but I am trying to get the simple case working first.
However! No matter what I try in the database rules simulator, despite what the documentation seems to suggest, the value of data.exists() seems to always be true. From what I what I can understand from the documentation, the variable data represents a record in the database as it did before an operation took-place. That is to say, for creates, data would not exist, and for updates/deletes, data would refer to a real record that exists in the database. This does not seem to be the case, to the point where I am actually suspecting a bug in Firebase, as when setting the following rules on my database, all write operations are disallowed:
{
"rules": {
".read": true,
".write": "!data.exists()"
}
}
No matter what values I put into the simulator, be it Location or Data. I have even written a small EmberJS app to verify if the Simulator is telling the truth and it too, is denied permission for all write operations.
I really have no idea where to go from here as I am pretty much out of things to try. I tried deleting all records from my database, which lets the simulator think it can perform write operations, but my test app still gets PERMISSION_DENIED, so I don't know what's causing inconsistencies there.
Is my understanding of the predefined data variable correct? If so, why can't I write the rules I want? I have seen snippets literally trying to achieve my "create only, no-delete" rule that seem to line up with my understanding.
Last note: I am trying this in a totally new Firebase project with JUST the rules above, and only ~a few records of junk data laying around my database.
Because you have placed the !data.exists() at the root location of your database, data refers to the entire database. You will only be able to write to the database when it is completely empty.
You indicate that you run your tests with only a few records of junk data laying around my database. Those records will cause data.exists() to be true.
You can achieve your goal by placing the !data.exists() rule in your tree at the specific location where you want to require that no data already exists. This is typically done at a location with a wildcard key, as in the example you linked:
{
"rules": {
// default rules are false if not specified
"posts": {
".read": true, // everyone can read all posts
"$postId": {
// a new post can be created if it does not exist
// existing posts can only be edited by their original "author"
".write": "!data.exists() && newData.exists() || data.child('author').val() == auth.uid",
".validate": "newData.hasChildren(['title', 'author', 'timestamp'])",
}
}
}
}

Firebase DB rules simulator allow read and write by unauthenticated users

The simulator allows read/write to Posts key, but the results are correct for the Users key rules. Each post under Posts has a uid value representing a user in Users key.
Are my rules wrong or is the simulator wrong? Be gentle, I'm new to Firebase. :)
Two equals:
Redacted Data view: https: // i.stack.imgur.com/GaYMj.png (remove spaces around "//")
Try changing your rules to check that a uid child exists. For example:
".read": "data.child('uid').exists() && data.child('uid').val() === auth.uid"
Based on a quick test, I think what is occuring is that when a uid child does not exist, the evaluation of data.child('uid').val() fails and is handled by assigning it a value of false. Similarly, because the user is not authenticated, auth is null and auth.uid also evaluates to false. So your rule effectively becomes ".read": "false === false", which is true.
When I first simulated a read using your rule and I did not have a uid child in my database under /posts/1, the read was granted, as you reported. When I added a uid child, it was not granted.

Resources