Firebase: Proper database filter on newData - firebase

I am learning how firebase database filters work, and i haven't get a clue on other answers as to how this really works, i have also been reading the docs to see if i could understand it's whole functionality but yet i cannot get my goal (probably i am reading the wrong section)
I have a simple document to insert in the database as follows:
{
"name": "new project"
}
right now i want to apply rules for name to validate if it is a number just for testing (upon figuring this out, i will set more complex conditions). So i have made a simple database rule structure for this scenario:
{
"rules": {
".read": "auth != null",
".write": "auth != null",
"projects": {
"$project": {
"name": {
".validate": "newData.isNumber()" // also tried newData.child('name').isNumber()
}
}
}
}
}
if i understood this right $project is the id of the new document to insert and will have the values passed on the inserting document.
I have been using the rule simulator to check on my progress of the rules but everytime i try this with a different code it always go on Simulated write allowed as long as i am authenticated.
So again, how can i apply the rules for the newData.prop in order to set the database a secure schema?

Related

How to add rule in Firebase to prevent read of parent element?

I have a Firebase database that I want to only allow users who have access to that application to be able to read from and write to.
My data structure is like so:
{
"applications": {
"id_1": {
"feature": {
"a": true,
"b": false
},
"users": {
"user_id_1": true,
"user_id_2": true
}
}
}
}
As you can see, each application can have many users who have read/write access.
I only want users in the users object to be able to retrieve that application.
I have rules like so:
{
"rules": {
"applications": {
".read": "auth != null",
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
".read": "auth != null", allows any user who is logged in to be able to retrieve all applications. I only want users user_id_1 or user_id_2 to be able to read that application.
In pseudo code, I would do something like this:
{
"rules": {
"applications": {
".read": "only users in `root.applications.$appId.users` can read", // I need to replace `$appId` some how
".write": "auth != null",
"$appId": {
".write": "data.child('users').child(auth.uid).val() === true",
".read": "data.child('users').child(auth.uid).val() === true"
}
}
}
}
How can I restrict it so when user user_id_1 fetches their applications, they only see apps they have access to?
You're hitting a few common problems here, so let's go through them one by one.
1. Security rules can't filter data
You're trying to control in your rule on /applications what application will be returned when a user tries to read that node. Unfortunately that is not possible, because security rules grant all-or-nothing access.
So either the user has access to /applications (and all data under it), or they don't have access to it. You can't set a rule on /applications to grant them access to some child nodes.
In the documentation, this is referred to as rules are not filters and the fact that permission cascades.
2. Avoid nesting data
You're trying to grant access to /applications, but then store two types of data under there for each application. In cases like that, it is usually better to store each type of data as its own top-level list.
So in your case, that'd be:
{
"application_features": {
"id_1": {
"a": true,
"b": false
},
},
"application_users": {
"id_1": {
"user_id_1": true,
"user_id_2": true
}
}
}
This allows you to grant separate access permissions for the application users and its features. While it means you'll have to read from both branches to get all information of each user, the performance difference there is negligible as Firebase pipelines those requests over a single socket
For controlling access and the most scalable data structure, Firebase recommends that you avoid nesting data and flatten your data structure.
3. Model the data in your database to reflect the screens of your app
Since granting anyone access on /applications gives them access to all data under that, you'll likely need another place to store the list of applications for each user.
I usually make this list explicit in my databases, as another top-level list:
{
...
"user_applications": {
"user_id_1": {
"id_1": true
},
"user_id_2": {
"id_1": true
}
}
}
So now when you want to show the list of applications for the current user, you load the IDs from /user_applications/$uid and then look up the additional information for each app with extra calls (which in turn can be pipelined again).
This one is not in the documentation, but a common pattern with NoSQL databases. I recommend checking out my answers to Many to Many relationship in Firebase and Firebase query if child of child contains a value.

firebase rule to make sure a owner of comment has access to read, write and any other user to only like it. number_check is the user table

The following is the database structure and i want to make sure the owner of the comment whose user_id is part of the comment object to have read and write access to the comment and all other users have read access to comment and the ability to like it to increase like_count:
The following is the security rule I came up with:
{
"rules": {
"comments": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().child('users').child(auth.uid).val() != null && newData.parent().child('comments').hasChildren().hasChildren().child('user_id').val() == auth.uid)",
"$commentId": {
".read": "root.child('users').child(auth.uid).val() != null",
".write": "(newData.parent().parent().child('users').child(auth.uid).val() != null && !newData.parent().parent().child('comments').hasChildren().hasChildren().child('like_count'))"
}
}
}
}
So, for read better will be:
".read": "root.child('users').hasChild(auth.uid)"
".write" is more complicated:
You need to only allow edit in like_count, and you need to allow for only one like per user
Proper way to do this will be expand structure of your "post", something like like_list, here or in other place to protect data transfer from growing when post will be download by client
So in "comment" node you want to allow every auth user to .write and .read, they will be able to add new comment then (upper rules will be fine for this).
For $commentId node rules will look like:
".write":".data.child('user_id').val() == auth.uid"
You can link like_list to a clinet by adding user_id to the "like / $ commentId" node and setting listener in firebase funtions for this node. adding new user_id will fire "write" event and then call function to secure increase the value of like_count.
You can archive it with a firebase-function and transaction.
https://www.tutorialspoint.com/firebase/firebase_write_transactional_data.htm
If you really don`t want to change schema then you need to secure every single child of comment, like:
"$commentID":{
"comment":{ ".write":"".data.parent().child('user_id').val() == auth.uid" ),
...//things allowed to edit by owner
"like_count":{
".write":"root.child('users').hasChild(auth.uid)",
".validate":"newData.val() == data.val() + 1"
}
}
But this will not secure post from giving more than one like per user.

Trouble implementing firebase rules

I'm having issues reading and writing data from my database on the client (my iOS app) using the following database rules:
// Checks auth uid equals database node uid
// In other words, the User can only access their own data
{
"rules": {
"posts": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
However, I have no issues reading and writing my data when using the following rule:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
My goal is to have each user only have the ability to read/write their own data. Any suggestions/help would be appreciated.
EDIT:
When attempting to post I use the following (iOS):
let key = ref?.childByAutoId().key
let post = ["uid": key,
"title": titleField.text,
"description": descField.text]
ref?.child(key!).setValue(post)
When I want to retrieve those data entries, currently I am attempting to retrieve the data entries by looking at the reference point ("task") (in iOS, my database reference is the following):
ref = Database.database().reference().child("task")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject]
else {
return
There are a couple things going on here.
First as Doug answered, your rules have to match your query.
Second, when using childByAutoId() you are creating a random key. Instead you should be using the UID of the logged in user.
And third when reading from the database you have to make sure to read from the location where you have placed your rules. Currently you are trying to read the entire list were there are no rules defined. (When no rules are defined Firebase defaults to false) Instead you should read the child of the list.
You can also take a look at my answer here for some more explenation and links to relevant docs.
Your rules don't match your queries. Your query is accessing documents at a node called "task", but your rules are protecting a node called "posts". You should edit your rules to match the queries you intend to protect.

Firebase Database rules ignore child rule

I am trying to add a simple rule to firebase database. the child node activities have proper access but the leads don't. if I sent the parent read true and then child rules are ignored. how can I set parent true to public but child restricted?
{
"rules": {
"leads": {
"$activity": {
".read":
"root.child('users_business_activities').
child(auth.uid).hasChild(data.child('category').val())",
".write" :
"root.child('users').child(auth.uid).child('isAdmin').val() == true"
}
},
"users": {
"$uid": {
".read": "auth.uid == true",
".write": "auth.uid == $uid"
}
},
"business_activities": {
".read": "auth.uid == true",
".write": "false"
}
}
}
In the Firebase Realtime Database permissions cascade downwards. Once you give a user read permission on /leads, you can't take that permission away lower in the JSON tree.
This has a few consequences:
Security rules cannot be used to filter data. This is known as rules are not filters in the Firebase documentation. I also recommend reading some of the many existing questions on this topic.
You will often need to create a lookup list of the activity IDs that a user has access to. Such a lookup list is often known as an "index" in Firebase terms. The documentation on creating scalable data structures has a good example of such a structure.
If part of the data for an activity/lead needs to be publicly readable, and part needs to remain private, you'll want to split the public and private parts into separate top-level nodes. This splitting is known as flattening you data structure in the docs. I also gave an answer here to show how to use this for user profile information.

Understanding Firebase's rules for user-write, global-read

I am building a simple Firebase application with AngularJS. This app authenticates users through Google. Each user has a list of books. Anyone can see books, even if they are not authenticated. Only the creator of a book can edit it. However, individual users need to be able to record that they've read a book even if someone else added it.
I have rules.json like so:
{
"rules": {
".read": false,
".write": false,
"book": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
}
".read": true,
}
}
}
And I am trying to write a book simply with:
$firebaseArray(new Firebase(URL + "/book")).$add({foo: "bar"})
I get a "permission denied" error when trying to do this although I do seem to be able to read books I create manually in Forge.
I also think that the best way to store readers would be to make it a property of the book (a set of $uid for logged-in readers). ".write" seems like it would block this, so how would I do that?
"$uid": {
".write": "auth !== null && auth.uid === $uid",
"readers": {
".write": "auth !== null"
}
},
It seems like a validation rule would be appropriate here as well ... something like newData.val() == auth.uid, but I'm not sure how to validate that readers is supposed to be an array (or specifically a set) of these values.
Let's start with a sample JSON snippet:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"title": "Structuring Data",
"url": "https://www.firebase.com/docs/web/guide/structuring-data.html",
"creator": "twiter:4916627"
},
"-JRHTHaKuITFIhnj02kE": {
"title": "Securing Your Data",
"url": "https://www.firebase.com/docs/security/guide/securing-data.html",
"creator": "twiter:209103"
}
}
So this is a list with two links to articles. Each link was added by a different user, who is identified by creator. The value of creator is a uid, which is a value that Firebase Authentication provides and that is available in your security rules under auth.uid.
I'll split your rule into two parts here:
{
"rules": {
".read": false,
"book": {
".read": true,
}
}
}
As far as I see your .read rule is correct, since your ref is to the /book node.
$firebaseArray(new Firebase(URL + "/book"))
Note that the ref below would not work, since you don't have read-access to the top-level node.
$firebaseArray(new Firebase(URL))
Now for the .write rules. First off is that you'll need to grant users write-access on the book level already. Calling $add means that you're adding a node under that level, so write-access is required.
{
"rules": {
"book": {
".write": "auth != null"
}
}
}
I leave the .read rules out here for clarity.
This allows any authenticated user to write to the book node. This means that they can add new books (which you want) and change existing books (which you don't want).
Your last requirement is most tricky. Any user can add a book. But once someone added a book, only that person can modify it. In Firebase's Security Rules, you'd model that like:
{
"rules": {
"book": {
".write": "auth != null",
"$bookid": {
".write": "!data.exists() || auth.uid == data.child('creator').val()"
}
}
}
}
In this last rule, we allow writing of a specific book if either there is no current data in this location (i.e. it's a new book) or if the data was created by the current user.
In the above example $bookid is just a variable name. The important thing is that the rule under it is applied to every book. If needed we could use $bookid in our rules and it would hold -JRHTHaIs-jNPLXOQivY or -JRHTHaKuITFIhnj02kE respectively. But in this case, that is not needed.
First off the "permission denied" error. You are getting this error because you are trying to write directly in the "book" node instead of "book/$uid".
Example of what you do now:
"book": {
"-JRHTHaIs-jNPLXOQivY": { //this is the generated unique id
"foo": "bar"
},
"-JRHTHaKuITFIhnj02kE": {
"foo": "bar"
}
}
In your rules you have a global rule for write set to false so that will be the default and next to that you have made a rule for the specific node book/$uid. So when trying to write directly in "book" it will take the default rule that was set to false. Have a look at Securing your data for more information about firebase rules.
And for the last part of your question i suggest you take a look at Structuring data for more information about the best ways to structure your data inside firebase.
So taka a good look at what and how you want to save and write in firebase and make sure your rules are structured accordingly.

Resources