I have created a database in Firebase which looks like:
Now I go into a REST client and issue this query:
https://movielens3.firebaseio.com/movieLens/users.json?orderBy="age"&startAt=25&print=pretty
It gives me an error:
"error": "Index not defined, add ".indexOn": "age", for path "/movieLens/users", to the rules"
So I go in the rule section and define this rule:
{
"rules": {
"users" : {
".indexOn": ["age", "gender", "occupation", "zipCode"]
},
".read": true,
".write": true
}
}
But I still get the same error.
You're defining indexes for /users. There is no child node users straight under the root of your tree, so those indexes will be empty.
Since you query /movieLens/users, that's exactly where the index has to be defined:
{
"rules": {
"movieLens": {
"users" : {
".indexOn": ["age", "gender", "occupation", "zipCode"]
},
".read": true,
".write": true
}
}
}
Update for problem in the comments:
You're storing the user's age as a string, so cannot filter them as a number. The solution is to fix your data and store it as a number.
But in the meantime this query somewhat works:
https://movielens3.firebaseio.com/movieLens/users.json?orderBy="age"&equalTo="35"
In JavaScript:
ref
.orderByChild('age')
.startAt('35')
.limitToFirst(3)
.once('value', function(s) {
console.log(JSON.stringify(s.val(), null, ' '));
}, function(error) {
if(error) console.error(error);
})
The "somewhat" being that it does a lexical sort, not a numerical one. Fix the data for a real solution.
Note that you're also ignoring Firebase guidance on structuring data by storing the data as an array. This was already causing problems for me in the REST API, which is why I dropped the print=pretty. I highly recommend that you read the Firebase programming guide for JavaScript developers from start to finish and follow the advice in there. The few hours that takes now, will prevent many hours of problems down the line.
Related
I try to get my firebase realtime database rules running correct but have a problem with a single property rule.
My firebase object looks like this example
"products": {
"KUg68BknfYWuEjAKla5": {
"cat": "Pizzas",
"likes": 132,
"item_price": 39.9,
"item_price_single": 39.9,
"name": "Mussarela",
"observation": "",
"product_id": "-KUg68BknfYWuEjAKla5",
"product_image": "massapan-mussarela.jpg",
"quantity": 1
}
My rules for this object look right now like this
"products": {
".read": true,
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'"
".indexOn": ["status", "categoryId"]
},
So basically I allow everybody to read the object but only the admin to write the object. My problem is that the single property "likes" need to be writeable by every authenticated user. Which I would normally do with ".read": "auth != null", but I dont know how to combine them in my rules. Can I set multiple lines with .write? do I need to combine them in one line? I tried all I can think of but without success.
thx in advance
You can specify access to specific child nodes within the rules. For example
products
product_0
likes: 123
item_price: 1.99
product_1
likes: 222
item_price: 4.99
rules that would only allow reading the likes node by all but limit writing to the admin would look something like this (not tested but something along these lines)
{
"rules": {
"products": {
"$each_product": {
"likes": {
".read": true,
".write": "root.child('users').child(auth.uid).child('admin').val() == 'user_is_admin'"
},
"item_price": {
".read": true,
".write": true
}
}
}
}
}
On the other hand the item_price node could be written to and read by all. None of the other child nodes would be accessible by anyone.
Instead of storing the amount of likes, you could make a list of users who have liked that category/item. Something like this:
likes: {
userid_1: true,
userid_2: true
}
That way, you can allow users to edit only their path. Just like you'd normally do with a user list:
".write": "auth.uid === $user"
The true value can be anything really, as users who didn't like the content wont be in the list.
You'll just need to count the number of items in the list to get the number of likes.
And no, you can't have multiple write rules. Instead, use ".validate". Read and write rules cascade, so if one is true, all the others will be ignored. Validate rules don't cascade, they all need to be true
Perhaps I'm tackling this problem too much from an SQL kind of perspective, but I'm having troubles understanding how to properly restrict which children should be allowed to populate a node.
Say that I want to keep a record of products with arbitrary names. Each product must contain a price, but nothing else is allowed.
My naive approach was to add a .validate rule to the products requiring newData to contain a price child, explicitly granting write access to the price node and then removing all access an $other node (somewhat like a default clause in a switch statement):
{
"rules": {
"$product": {
".read": true,
".write": true,
".validate": "newData.hasChildren(['price'])",
"price": {
".write": true,
".validate": "newData.isNumber()"
},
"$other": {
".read.": false,
".write": false,
}
}
}
}
This does not work. Adding a new product with {"price": 1234, "foo": "bar"} will still be accepted. If I however add a ".validate": false rule to $other, nothing is accepted instead (e.g. {"price": 1234} is not allowed). (I did that wrong, somehow.)
Is there some way to implement something similar to what I'm trying to do here? If not, what is the proper way of restricting a data structure in Firebase? Should I do it at all? What stops the user from filling my database with trash if I don't?
You're falling into a few common Firebase security pits here. The most common one is that permission cascades down: once you've granted read or write permission on a certain level in the tree, you cannot take that permission away at a lower level.
That means that these rules are ineffectual (since you've granted read/write one level higher already):
"$other": {
".read.": false,
".write": false,
}
To solve the problem you must realize that .validate rules are different: data is only considered valid when all validation rules are met. So you can reject the $other data with a validation rules:
{
"rules": {
"$product": {
".read": true,
".write": true,
".validate": "newData.hasChildren(['price'])",
"price": {
".validate": "newData.isNumber()"
},
"$other": {
".validate": false
}
}
}
}
Default setting gives full access:
{
"rules": {
".read": true,
".write": true
}
}
For the sake of testing my understanding of rule-writing from the Firebase guide and documentation, I'm (now retreating to) trying to achieve the same results by writing rules for the 4 parent nodes.
If it makes a difference, the first two nodes only have values, no children. **Sidequest: are they still called nodes?
The rules below cause the same behavior as when the rules above are changed to false read and write.
{
"rules": {
"myNode1": {
".read" : true,
".write" : true
},
"myNode2" : {
".read" : true,
".write" : true
},
"myNode3" : {
".read" : true,
".write" : true
},
"myNode4" : {
".read" : true,
".write" : true
}
}
}
What is wrong with my rules?
UPDATE/context:
I have an authDataCallback that stops running here (within the if (authData) { clause):
var ref = new Firebase("https://<my>.firebaseio.com")
ref.once("value", function(snapshot){
Found that if I change the ref var to something more specific: var ref = new Firebase("https://<my>.firebaseio.com/myNode1"), the authDataCallback runs in entirety.
Surely it won't be necessary to produce snapshots of the entire database; this was a confused way of getting the data I need. I've updated, but I'm still confused about why the rules held up the callback considering I gave read and write to the whole database.
I think this is the answer based on the info provided:
The two sets of rules you posted are different. The first set
{
"rules": {
".read": true,
".write": true
}
}
allows everyone read and write access to every node, including the parent node, within your firebase at
https://<my>.firebaseio.com
The second set of rules allows everyone access to specific nodes within your firebase reference but blocks access to all other nodes including access to the parent node. In the update the code is trying to read the parent node which has no rules defined for it so by default read and write are false.
So say you have the following structure:
https://whirlygig.firebaseio.com
Whirlygig
someRandomData: "3.141"
otherRandomData: "6.02"
myNode1
firstName: "first"
lastName: "last"
myNode2
first: "first"
last: "last"
With your first set of rules, everyone can access someRandomData as well as the myNode1, myNode2 etc.
With the second set of rules, everyone can access ONLY myNode1 and myNode2 but cannot access someRandomData
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.
For the past few weeks i've been exploring Firebase and its features to build a web app, but I've kind of ran into a wall when it comes to security rules.
I've build a data structure on Firebase but I'm not sure if it follows best practices (if it doesn't, feel free to suggest anything different about it):
{
"groups" : {
<GROUP_KEY>
"name": "",
"rels": {
"users": {
<RELS_USERS_KEY>
"key":"" (USER_KEY)
},
"notes": {
<RELS_NOTES_KEY>
"key":"" (NOTE_KEY)
}
},
"isPrivate": true
},
"users": {
<USER_KEY>
"email": "",
"rels": {
"friends": {
<RELS_FRIENDS_KEY>
"key":"" (USER_KEY)
}
},
},
"notes": {
<NOTE_KEY>
"title": "",
"description": "",
"rels": {
"files": {
<RELS_FILES_KEY>
"key":"" (FILE_KEY)
}
}
},
"files": {
<FILE_KEY>
"mode": "",
"url": ""
}
}
The application flow is as follows:
The user signs up: a key is created on "users";
Is redirected to "Groups" view, where he should be shown only
groups that have his ID in RELS > USERS, or that has
"isPrivate":"false";
As the user creates a Group, a new group is added with his ID in RELS > USERS;
Entering the Group view, he should only see notes that are in RELS > NOTES for that group.
The rest of the logic follows the same principle, and I believe that if I can get through the first hurdle of understanding the Firebase security rules and applying them to this case, I can get through the rest.
I've tried a couple of rules, but I can't seem to get any feedback at all from the web application, debugging this has been a trial-and-error process, and its not really working.
Could someone help me at least understanding the logic behind it ? I've read all of their tutorials but they all seem very shallow with no deeper examples on complex structures.
Thanks for the help.
EDIT
I've added the debug:true flag to the login (thanks #Kato), but I'm still getting no feedback on the rules. With the rules as below, I still enter the "Groups" view, but get no feedback on the console, and the logged-in user sees groups he shouldn't:
{
"rules": {
"groups": {
".read": "data.child('rels').child('users/' + auth.user).exists()",
".write": "data.child('rels').child('users/' + auth.user).exists()"
}
}
}
As for the rules I've tried, they were countless, but this is the most recent one (still no feedback).
Maybe I'm missing something ?
Thanks again.
Rules cascade. That is, if any rule allows read, then you cannot revoke it later in a nested child. In this way, you can write rules like the following:
"$record": {
// I can write the entire record if I own it
".write": "data.child('owner').val() === auth.uid",
"foo": {
// anybody in my friends list can write to foo, but not anything else in $record
".write": "data.parent().child('friends/'+auth.uid).exists()"
},
"bar": {
// this is superfluous as permissions are only "granted" and never "revoked" by a child
".write": false
}
}
Note how, because I am the owner, I can also write to foo and to bar, even though bar has tried to revoke my read privilege.
So in your case above, your rules declaration lists read: true which allows full read access to the entire repo. Change that to false and you'll see better results.